diff --git a/.gitignore b/.gitignore index 11a7486bf53..93e14b79193 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ Thumbs.db node_modules/ .build/ extensions/**/dist/ +extensions/notebook-markdown-extensions/notebook-out/** /out*/ /extensions/**/out/ src/vs/server diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index b48798c9e9e..95ae002374e 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -29,550 +29,6 @@ { "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": 974714207, - "node_id": "MDU6TGFiZWw5NzQ3MTQyMDc=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-finalization", - "name": "api-finalization", - "color": "c5def5", - "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": 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": 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": 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/144", - "html_url": "https://github.com/microsoft/vscode/milestone/144", - "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", - "id": 6407294, - "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", - "number": 144, - "title": "March 2021", - "description": "", - "creator": { - "login": "isidorn", - "id": 1926584, - "node_id": "MDQ6VXNlcjE5MjY1ODQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/isidorn", - "html_url": "https://github.com/isidorn", - "followers_url": "https://api.github.com/users/isidorn/followers", - "following_url": "https://api.github.com/users/isidorn/following{/other_user}", - "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", - "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", - "organizations_url": "https://api.github.com/users/isidorn/orgs", - "repos_url": "https://api.github.com/users/isidorn/repos", - "events_url": "https://api.github.com/users/isidorn/events{/privacy}", - "received_events_url": "https://api.github.com/users/isidorn/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 252, - "closed_issues": 190, - "state": "open", - "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", - "due_on": null, - "closed_at": null - }, - "comments": 1, - "created_at": "2021-02-02T19:29:05Z", - "updated_at": "2021-03-09T16:12:17Z", - "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 - } - ] - }, - { - "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-finalization, api-proposal, custom-editors, feature-request, notebook]- [@lramos15](https://github.com/lramos15 \"Issue 115631 is assigned to lramos15\")\n\n" - } - ] - }, - { - "kind": 1, - "language": "markdown", - "value": "### Proposals", - "editable": true, - "outputs": [] - }, - { - "kind": 2, - "language": "github-issues", - "value": "$repo $milestone is:open label:api-proposal ", - "editable": true, - "outputs": [ - { - "mime": "x-application/github-issues", - "value": [ - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/118084", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/events", - "html_url": "https://github.com/microsoft/vscode/issues/118084", - "id": 821570132, - "node_id": "MDU6SXNzdWU4MjE1NzAxMzI=", - "number": 118084, - "title": "Add trigger reason to code actions", - "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": 431507554, - "node_id": "MDU6TGFiZWw0MzE1MDc1NTQ=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/editor-code-actions", - "name": "editor-code-actions", - "color": "c5def5", - "default": false, - "description": "Editor inplace actions (Ctrl + .)" - } - ], - "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/144", - "html_url": "https://github.com/microsoft/vscode/milestone/144", - "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", - "id": 6407294, - "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", - "number": 144, - "title": "March 2021", - "description": "", - "creator": { - "login": "isidorn", - "id": 1926584, - "node_id": "MDQ6VXNlcjE5MjY1ODQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/isidorn", - "html_url": "https://github.com/isidorn", - "followers_url": "https://api.github.com/users/isidorn/followers", - "following_url": "https://api.github.com/users/isidorn/following{/other_user}", - "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", - "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", - "organizations_url": "https://api.github.com/users/isidorn/orgs", - "repos_url": "https://api.github.com/users/isidorn/repos", - "events_url": "https://api.github.com/users/isidorn/events{/privacy}", - "received_events_url": "https://api.github.com/users/isidorn/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 252, - "closed_issues": 190, - "state": "open", - "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", - "due_on": null, - "closed_at": null - }, - "comments": 3, - "created_at": "2021-03-03T23:04:03Z", - "updated_at": "2021-03-04T01:38:44Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "## Problem\r\n\r\nFor JS/TS, if user makes a imprecise selection (such as only selecting part of an identifier name) and then manually requests code actions, we'd like to return refactorings as if the selection were expanded. However we don't want to do this automatically for implicitly requested code actions (which cause the light bulb to show up)\r\n\r\n## Proposal\r\nAdd a trigger reason to the code action context. This would let you know if the code actions have been manually requested or were triggered automatically", - "performed_via_github_app": null, - "score": 1 - }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/117506", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/117506/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/117506/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/117506/events", - "html_url": "https://github.com/microsoft/vscode/issues/117506", - "id": 814923063, - "node_id": "MDU6SXNzdWU4MTQ5MjMwNjM=", - "number": 117506, - "title": "Add an API for a notebook kernel to modify its list of supported languages at runtime.", - "user": { - "login": "brettfo", - "id": 926281, - "node_id": "MDQ6VXNlcjkyNjI4MQ==", - "avatar_url": "https://avatars.githubusercontent.com/u/926281?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/brettfo", - "html_url": "https://github.com/brettfo", - "followers_url": "https://api.github.com/users/brettfo/followers", - "following_url": "https://api.github.com/users/brettfo/following{/other_user}", - "gists_url": "https://api.github.com/users/brettfo/gists{/gist_id}", - "starred_url": "https://api.github.com/users/brettfo/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/brettfo/subscriptions", - "organizations_url": "https://api.github.com/users/brettfo/orgs", - "repos_url": "https://api.github.com/users/brettfo/repos", - "events_url": "https://api.github.com/users/brettfo/events{/privacy}", - "received_events_url": "https://api.github.com/users/brettfo/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": 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": "rebornix", - "id": 876920, - "node_id": "MDQ6VXNlcjg3NjkyMA==", - "avatar_url": "https://avatars.githubusercontent.com/u/876920?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/rebornix", - "html_url": "https://github.com/rebornix", - "followers_url": "https://api.github.com/users/rebornix/followers", - "following_url": "https://api.github.com/users/rebornix/following{/other_user}", - "gists_url": "https://api.github.com/users/rebornix/gists{/gist_id}", - "starred_url": "https://api.github.com/users/rebornix/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/rebornix/subscriptions", - "organizations_url": "https://api.github.com/users/rebornix/orgs", - "repos_url": "https://api.github.com/users/rebornix/repos", - "events_url": "https://api.github.com/users/rebornix/events{/privacy}", - "received_events_url": "https://api.github.com/users/rebornix/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "rebornix", - "id": 876920, - "node_id": "MDQ6VXNlcjg3NjkyMA==", - "avatar_url": "https://avatars.githubusercontent.com/u/876920?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/rebornix", - "html_url": "https://github.com/rebornix", - "followers_url": "https://api.github.com/users/rebornix/followers", - "following_url": "https://api.github.com/users/rebornix/following{/other_user}", - "gists_url": "https://api.github.com/users/rebornix/gists{/gist_id}", - "starred_url": "https://api.github.com/users/rebornix/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/rebornix/subscriptions", - "organizations_url": "https://api.github.com/users/rebornix/orgs", - "repos_url": "https://api.github.com/users/rebornix/repos", - "events_url": "https://api.github.com/users/rebornix/events{/privacy}", - "received_events_url": "https://api.github.com/users/rebornix/received_events", - "type": "User", - "site_admin": false - }, - { - "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/144", - "html_url": "https://github.com/microsoft/vscode/milestone/144", - "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", - "id": 6407294, - "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", - "number": 144, - "title": "March 2021", - "description": "", - "creator": { - "login": "isidorn", - "id": 1926584, - "node_id": "MDQ6VXNlcjE5MjY1ODQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/isidorn", - "html_url": "https://github.com/isidorn", - "followers_url": "https://api.github.com/users/isidorn/followers", - "following_url": "https://api.github.com/users/isidorn/following{/other_user}", - "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", - "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", - "organizations_url": "https://api.github.com/users/isidorn/orgs", - "repos_url": "https://api.github.com/users/isidorn/repos", - "events_url": "https://api.github.com/users/isidorn/events{/privacy}", - "received_events_url": "https://api.github.com/users/isidorn/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 252, - "closed_issues": 190, - "state": "open", - "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", - "due_on": null, - "closed_at": null - }, - "comments": 1, - "created_at": "2021-02-23T23:32:14Z", - "updated_at": "2021-02-25T14:56:35Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "\r\n\r\n\r\n\r\n\r\nThe `NotebookKernel` interfaces exposes a `supportedLanguages: string[]` field, but for kernels that support a dynamic list of languages, there's currently no way for that list to be updated in a meaningful way at runtime.\r\n\r\nE.g., the .NET Interactive notebook extension supports doing something like this: `#!connect -type=MySQL -name=sql -connectionString=\"abcd\"` and from that point forward, a cell language of `SQL` should be supported, but not necessarily before.\r\n\r\nOne way to accomplish this would be to add an event on `NotebookKernel` called `LanguagesChanged` or similar that either contained a new string array of the supported languages _or_ instructed any listeners that a new string array was present in the `supportedLanguages` field; I don't have a strong preference either way.", - "performed_via_github_app": null, - "score": 1 - }, { "url": "https://api.github.com/repos/microsoft/vscode/issues/117058", "repository_url": "https://api.github.com/repos/microsoft/vscode", @@ -606,16 +62,16 @@ }, "labels": [ { - "id": 869332220, - "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/api-proposal", - "name": "api-proposal", + "id": 974714207, + "node_id": "MDU6TGFiZWw5NzQ3MTQyMDc=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-finalization", + "name": "api-finalization", "color": "c5def5", "default": false, "description": "" } ], - "state": "open", + "state": "closed", "locked": false, "assignee": { "login": "sbatten", @@ -688,18 +144,18 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 249, + "closed_issues": 280, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T15:51:00Z", "due_on": null, "closed_at": null }, "comments": 1, "created_at": "2021-02-19T16:33:09Z", - "updated_at": "2021-02-22T18:49:22Z", - "closed_at": null, + "updated_at": "2021-03-09T18:24:07Z", + "closed_at": "2021-03-09T18:24:07Z", "author_association": "MEMBER", "active_lock_reason": null, "body": "```ts\r\nexport interface ExtensionContext {\r\n\r\n/**\r\n * Indicates that this is a fresh install of VS Code.\r\n */\r\nreadonly isNewInstall: boolean;\r\n}\r\n```", @@ -747,6 +203,15 @@ "default": false, "description": "" }, + { + "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": 869332220, "node_id": "MDU6TGFiZWw4NjkzMzIyMjA=", @@ -839,17 +304,17 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 249, + "closed_issues": 280, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T15:51:00Z", "due_on": null, "closed_at": null }, - "comments": 10, + "comments": 11, "created_at": "2021-02-18T01:20:24Z", - "updated_at": "2021-02-24T08:13:19Z", + "updated_at": "2021-03-15T15:26:39Z", "closed_at": null, "author_association": "MEMBER", "active_lock_reason": null, @@ -1028,11 +493,846 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 249, + "closed_issues": 280, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T15:51:00Z", + "due_on": null, + "closed_at": null + }, + "comments": 1, + "created_at": "2021-02-02T19:29:05Z", + "updated_at": "2021-03-09T16:12:17Z", + "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/110267", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/events", + "html_url": "https://github.com/microsoft/vscode/issues/110267", + "id": 739391278, + "node_id": "MDU6SXNzdWU3MzkzOTEyNzg=", + "number": 110267, + "title": "Pass telemetry enablement to extensions", + "user": { + "login": "sbatten", + "id": 6561887, + "node_id": "MDQ6VXNlcjY1NjE4ODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sbatten", + "html_url": "https://github.com/sbatten", + "followers_url": "https://api.github.com/users/sbatten/followers", + "following_url": "https://api.github.com/users/sbatten/following{/other_user}", + "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", + "organizations_url": "https://api.github.com/users/sbatten/orgs", + "repos_url": "https://api.github.com/users/sbatten/repos", + "events_url": "https://api.github.com/users/sbatten/events{/privacy}", + "received_events_url": "https://api.github.com/users/sbatten/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": 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": 414580097, + "node_id": "MDU6TGFiZWw0MTQ1ODAwOTc=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/telemetry", + "name": "telemetry", + "color": "c5def5", + "default": false, + "description": "Telemetry system issues" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "sbatten", + "id": 6561887, + "node_id": "MDQ6VXNlcjY1NjE4ODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sbatten", + "html_url": "https://github.com/sbatten", + "followers_url": "https://api.github.com/users/sbatten/followers", + "following_url": "https://api.github.com/users/sbatten/following{/other_user}", + "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", + "organizations_url": "https://api.github.com/users/sbatten/orgs", + "repos_url": "https://api.github.com/users/sbatten/repos", + "events_url": "https://api.github.com/users/sbatten/events{/privacy}", + "received_events_url": "https://api.github.com/users/sbatten/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "sbatten", + "id": 6561887, + "node_id": "MDQ6VXNlcjY1NjE4ODc=", + "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sbatten", + "html_url": "https://github.com/sbatten", + "followers_url": "https://api.github.com/users/sbatten/followers", + "following_url": "https://api.github.com/users/sbatten/following{/other_user}", + "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", + "organizations_url": "https://api.github.com/users/sbatten/orgs", + "repos_url": "https://api.github.com/users/sbatten/repos", + "events_url": "https://api.github.com/users/sbatten/events{/privacy}", + "received_events_url": "https://api.github.com/users/sbatten/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/144", + "html_url": "https://github.com/microsoft/vscode/milestone/144", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", + "id": 6407294, + "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", + "number": 144, + "title": "March 2021", + "description": "", + "creator": { + "login": "isidorn", + "id": 1926584, + "node_id": "MDQ6VXNlcjE5MjY1ODQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/isidorn", + "html_url": "https://github.com/isidorn", + "followers_url": "https://api.github.com/users/isidorn/followers", + "following_url": "https://api.github.com/users/isidorn/following{/other_user}", + "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", + "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", + "organizations_url": "https://api.github.com/users/isidorn/orgs", + "repos_url": "https://api.github.com/users/isidorn/repos", + "events_url": "https://api.github.com/users/isidorn/events{/privacy}", + "received_events_url": "https://api.github.com/users/isidorn/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 249, + "closed_issues": 280, + "state": "open", + "created_at": "2021-02-08T17:30:20Z", + "updated_at": "2021-03-16T15:51:00Z", + "due_on": null, + "closed_at": null + }, + "comments": 2, + "created_at": "2020-11-09T21:56:01Z", + "updated_at": "2021-03-09T18:26:17Z", + "closed_at": "2021-03-09T18:26:17Z", + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "Right now extensions use the configuration to determine if they should send telemetry; however, the cli flag is not passed to the extension host.\r\n\r\n```ts\r\nexport namespace env {\r\n export const enableTelemetry: boolean;\r\n\r\n export const onDidChangeEnableTelemetry: Event;\r\n}\r\n```\r\n", + "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": 974714207, + "node_id": "MDU6TGFiZWw5NzQ3MTQyMDc=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-finalization", + "name": "api-finalization", + "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/144", + "html_url": "https://github.com/microsoft/vscode/milestone/144", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", + "id": 6407294, + "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", + "number": 144, + "title": "March 2021", + "description": "", + "creator": { + "login": "isidorn", + "id": 1926584, + "node_id": "MDQ6VXNlcjE5MjY1ODQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/isidorn", + "html_url": "https://github.com/isidorn", + "followers_url": "https://api.github.com/users/isidorn/followers", + "following_url": "https://api.github.com/users/isidorn/following{/other_user}", + "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", + "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", + "organizations_url": "https://api.github.com/users/isidorn/orgs", + "repos_url": "https://api.github.com/users/isidorn/repos", + "events_url": "https://api.github.com/users/isidorn/events{/privacy}", + "received_events_url": "https://api.github.com/users/isidorn/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 249, + "closed_issues": 280, + "state": "open", + "created_at": "2021-02-08T17:30:20Z", + "updated_at": "2021-03-16T15:51:00Z", + "due_on": null, + "closed_at": null + }, + "comments": 22, + "created_at": "2020-08-30T21:21:23Z", + "updated_at": "2021-03-16T15:47:54Z", + "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 + } + ] + }, + { + "mime": "text/markdown", + "value": "- [#117058](https://github.com/microsoft/vscode/issues/117058 \"add property to extension api for new install\") add property to extension api for new install [api-finalization]- [@sbatten](https://github.com/sbatten \"Issue 117058 is assigned to sbatten\")\n\n- [#116906](https://github.com/microsoft/vscode/issues/116906 \"Add extension ID and version to ExtensionContext\") Add extension ID and version to ExtensionContext [api, api-finalization, api-proposal, feature-request]- [@eamodio](https://github.com/eamodio \"Issue 116906 is assigned to eamodio\")\n\n- [#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-finalization, api-proposal, custom-editors, feature-request, notebook]- [@lramos15](https://github.com/lramos15 \"Issue 115631 is assigned to lramos15\")\n\n- [#110267](https://github.com/microsoft/vscode/issues/110267 \"Pass telemetry enablement to extensions\") Pass telemetry enablement to extensions [api-finalization, feature-request, telemetry]- [@sbatten](https://github.com/sbatten \"Issue 110267 is assigned to sbatten\")\n\n- [#105690](https://github.com/microsoft/vscode/issues/105690 \"Extension API for Inline Values\") Extension API for Inline Values [api, api-finalization, debug, feature-request]- [@weinand](https://github.com/weinand \"Issue 105690 is assigned to weinand\")\n\n" + } + ] + }, + { + "kind": 1, + "language": "markdown", + "value": "### Proposals", + "editable": true, + "outputs": [] + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repo $milestone is:open label:api-proposal ", + "editable": true, + "outputs": [ + { + "mime": "x-application/github-issues", + "value": [ + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/119097", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/119097/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/119097/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/119097/events", + "html_url": "https://github.com/microsoft/vscode/issues/119097", + "id": 832954890, + "node_id": "MDU6SXNzdWU4MzI5NTQ4OTA=", + "number": 119097, + "title": "Allow extensions to contribute getting started content", + "user": { + "login": "JacksonKearl", + "id": 8586769, + "node_id": "MDQ6VXNlcjg1ODY3Njk=", + "avatar_url": "https://avatars.githubusercontent.com/u/8586769?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/JacksonKearl", + "html_url": "https://github.com/JacksonKearl", + "followers_url": "https://api.github.com/users/JacksonKearl/followers", + "following_url": "https://api.github.com/users/JacksonKearl/following{/other_user}", + "gists_url": "https://api.github.com/users/JacksonKearl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/JacksonKearl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/JacksonKearl/subscriptions", + "organizations_url": "https://api.github.com/users/JacksonKearl/orgs", + "repos_url": "https://api.github.com/users/JacksonKearl/repos", + "events_url": "https://api.github.com/users/JacksonKearl/events{/privacy}", + "received_events_url": "https://api.github.com/users/JacksonKearl/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": 2552283302, + "node_id": "MDU6TGFiZWwyNTUyMjgzMzAy", + "url": "https://api.github.com/repos/microsoft/vscode/labels/getting-started", + "name": "getting-started", + "color": "c5def5", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "JacksonKearl", + "id": 8586769, + "node_id": "MDQ6VXNlcjg1ODY3Njk=", + "avatar_url": "https://avatars.githubusercontent.com/u/8586769?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/JacksonKearl", + "html_url": "https://github.com/JacksonKearl", + "followers_url": "https://api.github.com/users/JacksonKearl/followers", + "following_url": "https://api.github.com/users/JacksonKearl/following{/other_user}", + "gists_url": "https://api.github.com/users/JacksonKearl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/JacksonKearl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/JacksonKearl/subscriptions", + "organizations_url": "https://api.github.com/users/JacksonKearl/orgs", + "repos_url": "https://api.github.com/users/JacksonKearl/repos", + "events_url": "https://api.github.com/users/JacksonKearl/events{/privacy}", + "received_events_url": "https://api.github.com/users/JacksonKearl/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "JacksonKearl", + "id": 8586769, + "node_id": "MDQ6VXNlcjg1ODY3Njk=", + "avatar_url": "https://avatars.githubusercontent.com/u/8586769?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/JacksonKearl", + "html_url": "https://github.com/JacksonKearl", + "followers_url": "https://api.github.com/users/JacksonKearl/followers", + "following_url": "https://api.github.com/users/JacksonKearl/following{/other_user}", + "gists_url": "https://api.github.com/users/JacksonKearl/gists{/gist_id}", + "starred_url": "https://api.github.com/users/JacksonKearl/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/JacksonKearl/subscriptions", + "organizations_url": "https://api.github.com/users/JacksonKearl/orgs", + "repos_url": "https://api.github.com/users/JacksonKearl/repos", + "events_url": "https://api.github.com/users/JacksonKearl/events{/privacy}", + "received_events_url": "https://api.github.com/users/JacksonKearl/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/microsoft/vscode/milestones/144", + "html_url": "https://github.com/microsoft/vscode/milestone/144", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", + "id": 6407294, + "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", + "number": 144, + "title": "March 2021", + "description": "", + "creator": { + "login": "isidorn", + "id": 1926584, + "node_id": "MDQ6VXNlcjE5MjY1ODQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/isidorn", + "html_url": "https://github.com/isidorn", + "followers_url": "https://api.github.com/users/isidorn/followers", + "following_url": "https://api.github.com/users/isidorn/following{/other_user}", + "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", + "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", + "organizations_url": "https://api.github.com/users/isidorn/orgs", + "repos_url": "https://api.github.com/users/isidorn/repos", + "events_url": "https://api.github.com/users/isidorn/events{/privacy}", + "received_events_url": "https://api.github.com/users/isidorn/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 250, + "closed_issues": 281, + "state": "open", + "created_at": "2021-02-08T17:30:20Z", + "updated_at": "2021-03-16T16:43:27Z", + "due_on": null, + "closed_at": null + }, + "comments": 0, + "created_at": "2021-03-16T16:02:03Z", + "updated_at": "2021-03-16T16:09:09Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "We want to allow extensions to contribute items to the welcome page's getting started section:\r\n![image](https://user-images.githubusercontent.com/8586769/111341118-74bf9280-8636-11eb-9589-56e6c5ce926d.png)\r\n![image](https://user-images.githubusercontent.com/8586769/111341319-a33d6d80-8636-11eb-97d2-35bb563b7c9e.png)\r\n\r\nUsing a package.json like this:\r\n```json\r\n\t\t\"welcomeCategories\": [\r\n\t\t\t{\r\n\t\t\t\t\"id\": \"exampleProject\",\r\n\t\t\t\t\"title\": \"Turn Markdown into HTML\",\r\n\t\t\t\t\"description\": \"Use this sample project to learn how to convert Markdown to HTML!\"\r\n\t\t\t}\r\n\t\t],\r\n\t\t\"welcomeItems\": {\r\n\t\t\t\"exampleProject\": [\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"md-to-html.openExample\",\r\n\t\t\t\t\t\"title\": \"Open an example folder\",\r\n\t\t\t\t\t\"description\": \"To start, try opening an example folder that has been preconfigured for this tutorial. This is optional, but helps for following along!\",\r\n\t\t\t\t\t\"button\": {\r\n\t\t\t\t\t\t\"title\": \"Open Example\",\r\n\t\t\t\t\t\t\"command\": \"md-to-html.openExample\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"media\": {\r\n\t\t\t\t\t\t\"path\": \"media/example-project.png\",\r\n\t\t\t\t\t\t\"altText\": \"example project\"\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"md-to-html.showPreview\",\r\n\t\t\t\t\t\"title\": \"Preview your Markdown\",\r\n\t\t\t\t\t\"description\": \"Open a markdown file and click the \\\"Open Preview\\\" button at the top of the screen to see a preview of your file. This is technically optional, but it updates live and is helpful to check when creating your content!\",\r\n\t\t\t\t\t\"button\": {\r\n\t\t\t\t\t\t\"title\": \"Open Markdown File\",\r\n\t\t\t\t\t\t\"command\": \"md-to-html.openMarkdown\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"media\": {\r\n\t\t\t\t\t\t\"path\": \"media/preview.png\",\r\n\t\t\t\t\t\t\"altText\": \"preview\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"doneOn\": {\"command\": \"markdown.showPreviewToSide\" } \r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"md-to-html.convertToHTML\",\r\n\t\t\t\t\t\"title\": \"Create your .html file\",\r\n\t\t\t\t\t\"description\": \"To create the html file, run \\\"Convert Document to HTML\\\" from the editor actions context menu, behind the three dots top of the screen. This will create an .html file along side the markdown file.\",\r\n\t\t\t\t\t\"button\": {\r\n\t\t\t\t\t\t\"title\": \"Open Markdown File\",\r\n\t\t\t\t\t\t\"command\": \"md-to-html.openMarkdown\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"media\": {\r\n\t\t\t\t\t\t\"path\": \"media/convert-option.png\",\r\n\t\t\t\t\t\t\"altText\": \"showing editor actions context menu\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"doneOn\": {\"command\": \"md-to-html.convertToHTML\" }\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"md-to-html.openInBrowser\",\r\n\t\t\t\t\t\"title\": \"Open your .html file in the browser\",\r\n\t\t\t\t\t\"description\": \"Test that everything worked by opening the .html file in your browser. First open the html file in vscode, then right click in the editor and choose \\\"Open in Default Browser\\\".\",\r\n\t\t\t\t\t\"button\": {\r\n\t\t\t\t\t\t\"title\": \"Open HTML File\",\r\n\t\t\t\t\t\t\"command\": \"md-to-html.openHTML\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"media\": {\r\n\t\t\t\t\t\t\"path\": \"media/open-in-browser.png\",\r\n\t\t\t\t\t\t\"altText\": \"use editor context menu to open an .html file in your browser\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"doneOn\": {\"command\": \"extension.openInDefaultBrowser\" }\r\n\t\t\t\t},\r\n\t\t\t\t{\r\n\t\t\t\t\t\"id\": \"md-to-html.addKeybinding\",\r\n\t\t\t\t\t\"title\": \"Create a keyboard shortcut\",\r\n\t\t\t\t\t\"description\": \"That's all! You can share that single .html file with anyone without needing to bundle the images. To make this even easier in the future, consider adding a keybinding for the \\\"Convert Document to HTML\\\" command.\",\r\n\t\t\t\t\t\"button\": {\r\n\t\t\t\t\t\t\"title\": \"Add a keybinding\",\r\n\t\t\t\t\t\t\"command\": \"md-to-html.addKeybinding\"\r\n\t\t\t\t\t},\r\n\t\t\t\t\t\"media\": {\r\n\t\t\t\t\t\t\"path\": \"media/add-keybinding.png\",\r\n\t\t\t\t\t\t\"altText\": \"use the keybindings editor to add a keybinding for this command\"\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t]\r\n\t\t},\r\n```\r\n\r\nThis is the change to the extension contributions interface:\r\n```ts\r\nexport interface IExtensionContributions {\r\n .....\r\n\twelcomeItems?: { [category: string]: IWelcomeItem[] };\r\n\twelcomeCategories?: IWelcomeCategory[];\r\n .....\r\n}\r\n```\r\n\r\n```ts\r\nexport interface IWelcomeItem {\r\n\treadonly id: string;\r\n\treadonly title: string;\r\n\treadonly description: string;\r\n\treadonly button: { title: string } & ({ command?: never, link: string } | { command: string, link?: never }),\r\n\treadonly media: { path: string | { hc: string, light: string, dark: string }, altText: string },\r\n\treadonly doneOn?:\r\n\t| { event: string; command?: never }\r\n\t| { event?: never; command: string };\r\n\treadonly when?: string;\r\n}\r\n\r\nexport interface IWelcomeCategory {\r\n\treadonly id: string,\r\n\treadonly title: string;\r\n\treadonly description: string;\r\n\treadonly when?: string;\r\n}\r\n```\r\n\r\n\r\n\r\n", + "performed_via_github_app": null, + "score": 1 + }, + { + "url": "https://api.github.com/repos/microsoft/vscode/issues/118084", + "repository_url": "https://api.github.com/repos/microsoft/vscode", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/118084/events", + "html_url": "https://github.com/microsoft/vscode/issues/118084", + "id": 821570132, + "node_id": "MDU6SXNzdWU4MjE1NzAxMzI=", + "number": 118084, + "title": "Add trigger reason to code actions", + "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": 431507554, + "node_id": "MDU6TGFiZWw0MzE1MDc1NTQ=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/editor-code-actions", + "name": "editor-code-actions", + "color": "c5def5", + "default": false, + "description": "Editor inplace actions (Ctrl + .)" + } + ], + "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/144", + "html_url": "https://github.com/microsoft/vscode/milestone/144", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", + "id": 6407294, + "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", + "number": 144, + "title": "March 2021", + "description": "", + "creator": { + "login": "isidorn", + "id": 1926584, + "node_id": "MDQ6VXNlcjE5MjY1ODQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/isidorn", + "html_url": "https://github.com/isidorn", + "followers_url": "https://api.github.com/users/isidorn/followers", + "following_url": "https://api.github.com/users/isidorn/following{/other_user}", + "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", + "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", + "organizations_url": "https://api.github.com/users/isidorn/orgs", + "repos_url": "https://api.github.com/users/isidorn/repos", + "events_url": "https://api.github.com/users/isidorn/events{/privacy}", + "received_events_url": "https://api.github.com/users/isidorn/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 250, + "closed_issues": 281, + "state": "open", + "created_at": "2021-02-08T17:30:20Z", + "updated_at": "2021-03-16T16:43:27Z", + "due_on": null, + "closed_at": null + }, + "comments": 4, + "created_at": "2021-03-03T23:04:03Z", + "updated_at": "2021-03-16T16:08:22Z", + "closed_at": null, + "author_association": "MEMBER", + "active_lock_reason": null, + "body": "## Problem\r\n\r\nFor JS/TS, if user makes a imprecise selection (such as only selecting part of an identifier name) and then manually requests code actions, we'd like to return refactorings as if the selection were expanded. However we don't want to do this automatically for implicitly requested code actions (which cause the light bulb to show up)\r\n\r\n## Proposal\r\nAdd a trigger reason to the code action context. This would let you know if the code actions have been manually requested or were triggered automatically", + "performed_via_github_app": null, + "score": 1 + }, + { + "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": 974714207, + "node_id": "MDU6TGFiZWw5NzQ3MTQyMDc=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/api-finalization", + "name": "api-finalization", + "color": "c5def5", + "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": 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": 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": 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/144", + "html_url": "https://github.com/microsoft/vscode/milestone/144", + "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", + "id": 6407294, + "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", + "number": 144, + "title": "March 2021", + "description": "", + "creator": { + "login": "isidorn", + "id": 1926584, + "node_id": "MDQ6VXNlcjE5MjY1ODQ=", + "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/isidorn", + "html_url": "https://github.com/isidorn", + "followers_url": "https://api.github.com/users/isidorn/followers", + "following_url": "https://api.github.com/users/isidorn/following{/other_user}", + "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", + "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", + "organizations_url": "https://api.github.com/users/isidorn/orgs", + "repos_url": "https://api.github.com/users/isidorn/repos", + "events_url": "https://api.github.com/users/isidorn/events{/privacy}", + "received_events_url": "https://api.github.com/users/isidorn/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 250, + "closed_issues": 281, + "state": "open", + "created_at": "2021-02-08T17:30:20Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, @@ -1190,11 +1490,11 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, @@ -1340,17 +1640,17 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, - "comments": 16, + "comments": 17, "created_at": "2021-02-02T15:37:45Z", - "updated_at": "2021-03-08T14:51:56Z", + "updated_at": "2021-03-16T00:33:17Z", "closed_at": null, "author_association": "MEMBER", "active_lock_reason": null, @@ -1500,11 +1800,11 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, @@ -1678,11 +1978,11 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, @@ -1696,157 +1996,6 @@ "performed_via_github_app": null, "score": 1 }, - { - "url": "https://api.github.com/repos/microsoft/vscode/issues/110267", - "repository_url": "https://api.github.com/repos/microsoft/vscode", - "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/labels{/name}", - "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/comments", - "events_url": "https://api.github.com/repos/microsoft/vscode/issues/110267/events", - "html_url": "https://github.com/microsoft/vscode/issues/110267", - "id": 739391278, - "node_id": "MDU6SXNzdWU3MzkzOTEyNzg=", - "number": 110267, - "title": "Pass telemetry enablement to extensions", - "user": { - "login": "sbatten", - "id": 6561887, - "node_id": "MDQ6VXNlcjY1NjE4ODc=", - "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sbatten", - "html_url": "https://github.com/sbatten", - "followers_url": "https://api.github.com/users/sbatten/followers", - "following_url": "https://api.github.com/users/sbatten/following{/other_user}", - "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", - "organizations_url": "https://api.github.com/users/sbatten/orgs", - "repos_url": "https://api.github.com/users/sbatten/repos", - "events_url": "https://api.github.com/users/sbatten/events{/privacy}", - "received_events_url": "https://api.github.com/users/sbatten/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": 414580097, - "node_id": "MDU6TGFiZWw0MTQ1ODAwOTc=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/telemetry", - "name": "telemetry", - "color": "c5def5", - "default": false, - "description": "Telemetry system issues" - } - ], - "state": "open", - "locked": false, - "assignee": { - "login": "sbatten", - "id": 6561887, - "node_id": "MDQ6VXNlcjY1NjE4ODc=", - "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sbatten", - "html_url": "https://github.com/sbatten", - "followers_url": "https://api.github.com/users/sbatten/followers", - "following_url": "https://api.github.com/users/sbatten/following{/other_user}", - "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", - "organizations_url": "https://api.github.com/users/sbatten/orgs", - "repos_url": "https://api.github.com/users/sbatten/repos", - "events_url": "https://api.github.com/users/sbatten/events{/privacy}", - "received_events_url": "https://api.github.com/users/sbatten/received_events", - "type": "User", - "site_admin": false - }, - "assignees": [ - { - "login": "sbatten", - "id": 6561887, - "node_id": "MDQ6VXNlcjY1NjE4ODc=", - "avatar_url": "https://avatars.githubusercontent.com/u/6561887?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/sbatten", - "html_url": "https://github.com/sbatten", - "followers_url": "https://api.github.com/users/sbatten/followers", - "following_url": "https://api.github.com/users/sbatten/following{/other_user}", - "gists_url": "https://api.github.com/users/sbatten/gists{/gist_id}", - "starred_url": "https://api.github.com/users/sbatten/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/sbatten/subscriptions", - "organizations_url": "https://api.github.com/users/sbatten/orgs", - "repos_url": "https://api.github.com/users/sbatten/repos", - "events_url": "https://api.github.com/users/sbatten/events{/privacy}", - "received_events_url": "https://api.github.com/users/sbatten/received_events", - "type": "User", - "site_admin": false - } - ], - "milestone": { - "url": "https://api.github.com/repos/microsoft/vscode/milestones/144", - "html_url": "https://github.com/microsoft/vscode/milestone/144", - "labels_url": "https://api.github.com/repos/microsoft/vscode/milestones/144/labels", - "id": 6407294, - "node_id": "MDk6TWlsZXN0b25lNjQwNzI5NA==", - "number": 144, - "title": "March 2021", - "description": "", - "creator": { - "login": "isidorn", - "id": 1926584, - "node_id": "MDQ6VXNlcjE5MjY1ODQ=", - "avatar_url": "https://avatars.githubusercontent.com/u/1926584?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/isidorn", - "html_url": "https://github.com/isidorn", - "followers_url": "https://api.github.com/users/isidorn/followers", - "following_url": "https://api.github.com/users/isidorn/following{/other_user}", - "gists_url": "https://api.github.com/users/isidorn/gists{/gist_id}", - "starred_url": "https://api.github.com/users/isidorn/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/isidorn/subscriptions", - "organizations_url": "https://api.github.com/users/isidorn/orgs", - "repos_url": "https://api.github.com/users/isidorn/repos", - "events_url": "https://api.github.com/users/isidorn/events{/privacy}", - "received_events_url": "https://api.github.com/users/isidorn/received_events", - "type": "User", - "site_admin": false - }, - "open_issues": 252, - "closed_issues": 190, - "state": "open", - "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", - "due_on": null, - "closed_at": null - }, - "comments": 2, - "created_at": "2020-11-09T21:56:01Z", - "updated_at": "2021-03-08T19:03:37Z", - "closed_at": null, - "author_association": "MEMBER", - "active_lock_reason": null, - "body": "Right now extensions use the configuration to determine if they should send telemetry; however, the cli flag is not passed to the extension host.\r\n\r\n```ts\r\nexport namespace env {\r\n export const enableTelemetry: boolean;\r\n\r\n export const onDidChangeEnableTelemetry: Event;\r\n}\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", @@ -1980,11 +2129,11 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, @@ -2160,17 +2309,17 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, - "comments": 61, + "comments": 62, "created_at": "2020-09-25T17:19:53Z", - "updated_at": "2021-02-22T23:40:30Z", + "updated_at": "2021-03-16T08:33:01Z", "closed_at": null, "author_association": "MEMBER", "active_lock_reason": null, @@ -2179,46 +2328,37 @@ "score": 1 }, { - "url": "https://api.github.com/repos/microsoft/vscode/issues/105690", + "url": "https://api.github.com/repos/microsoft/vscode/issues/77423", "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", + "labels_url": "https://api.github.com/repos/microsoft/vscode/issues/77423/labels{/name}", + "comments_url": "https://api.github.com/repos/microsoft/vscode/issues/77423/comments", + "events_url": "https://api.github.com/repos/microsoft/vscode/issues/77423/events", + "html_url": "https://github.com/microsoft/vscode/issues/77423", + "id": 468297758, + "node_id": "MDU6SXNzdWU0NjgyOTc3NTg=", + "number": 77423, + "title": "Add title property to QuickPickOptions (showQuickPick)", "user": { - "login": "weinand", - "id": 1898161, - "node_id": "MDQ6VXNlcjE4OTgxNjE=", - "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "login": "letmaik", + "id": 530988, + "node_id": "MDQ6VXNlcjUzMDk4OA==", + "avatar_url": "https://avatars.githubusercontent.com/u/530988?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", + "url": "https://api.github.com/users/letmaik", + "html_url": "https://github.com/letmaik", + "followers_url": "https://api.github.com/users/letmaik/followers", + "following_url": "https://api.github.com/users/letmaik/following{/other_user}", + "gists_url": "https://api.github.com/users/letmaik/gists{/gist_id}", + "starred_url": "https://api.github.com/users/letmaik/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/letmaik/subscriptions", + "organizations_url": "https://api.github.com/users/letmaik/orgs", + "repos_url": "https://api.github.com/users/letmaik/repos", + "events_url": "https://api.github.com/users/letmaik/events{/privacy}", + "received_events_url": "https://api.github.com/users/letmaik/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=", @@ -2228,15 +2368,6 @@ "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=", @@ -2247,55 +2378,75 @@ "description": "Request for new features or functionality" }, { - "id": 635068821, - "node_id": "MDU6TGFiZWw2MzUwNjg4MjE=", - "url": "https://api.github.com/repos/microsoft/vscode/labels/on-testplan", - "name": "on-testplan", - "color": "E2A1C2", + "id": 527005453, + "node_id": "MDU6TGFiZWw1MjcwMDU0NTM=", + "url": "https://api.github.com/repos/microsoft/vscode/labels/quick-pick", + "name": "quick-pick", + "color": "c5def5", "default": false, - "description": "VS Code - Issue added to test plan" + "description": "Quick-pick widget issues" } ], "state": "open", "locked": false, "assignee": { - "login": "weinand", - "id": 1898161, - "node_id": "MDQ6VXNlcjE4OTgxNjE=", - "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "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/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", + "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": "weinand", - "id": 1898161, - "node_id": "MDQ6VXNlcjE4OTgxNjE=", - "avatar_url": "https://avatars.githubusercontent.com/u/1898161?v=4", + "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/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", + "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": "chrmarti", + "id": 9205389, + "node_id": "MDQ6VXNlcjkyMDUzODk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9205389?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/chrmarti", + "html_url": "https://github.com/chrmarti", + "followers_url": "https://api.github.com/users/chrmarti/followers", + "following_url": "https://api.github.com/users/chrmarti/following{/other_user}", + "gists_url": "https://api.github.com/users/chrmarti/gists{/gist_id}", + "starred_url": "https://api.github.com/users/chrmarti/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/chrmarti/subscriptions", + "organizations_url": "https://api.github.com/users/chrmarti/orgs", + "repos_url": "https://api.github.com/users/chrmarti/repos", + "events_url": "https://api.github.com/users/chrmarti/events{/privacy}", + "received_events_url": "https://api.github.com/users/chrmarti/received_events", "type": "User", "site_admin": false } @@ -2329,21 +2480,21 @@ "type": "User", "site_admin": false }, - "open_issues": 252, - "closed_issues": 190, + "open_issues": 250, + "closed_issues": 281, "state": "open", "created_at": "2021-02-08T17:30:20Z", - "updated_at": "2021-03-09T16:43:04Z", + "updated_at": "2021-03-16T16:43:27Z", "due_on": null, "closed_at": null }, - "comments": 18, - "created_at": "2020-08-30T21:21:23Z", - "updated_at": "2021-03-08T08:55:19Z", + "comments": 2, + "created_at": "2019-07-15T19:31:48Z", + "updated_at": "2021-03-16T16:43:27Z", "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", + "body": "Currently, `title` can only be set when going through the more complex `createQuickPick` API. It would be great if the title could be set via `QuickPickOptions` when using the simpler `showQuickPick`.", "performed_via_github_app": null, "score": 1 } @@ -2351,7 +2502,7 @@ }, { "mime": "text/markdown", - "value": "- [#118084](https://github.com/microsoft/vscode/issues/118084 \"Add trigger reason to code actions\") Add trigger reason to code actions [api, api-proposal, editor-code-actions]- [@mjbvz](https://github.com/mjbvz \"Issue 118084 is assigned to mjbvz\")\n\n- [#117506](https://github.com/microsoft/vscode/issues/117506 \"Add an API for a notebook kernel to modify its list of supported languages at runtime.\") Add an API for a notebook kernel to modify its list of supported languages at runtime. [api, api-proposal, feature-request, notebook]- [@rebornix](https://github.com/rebornix \"Issue 117506 is assigned to rebornix\")\n\n- [#117058](https://github.com/microsoft/vscode/issues/117058 \"add property to extension api for new install\") add property to extension api for new install [api-proposal]- [@sbatten](https://github.com/sbatten \"Issue 117058 is assigned to sbatten\")\n\n- [#116906](https://github.com/microsoft/vscode/issues/116906 \"Add extension ID and version to ExtensionContext\") Add extension ID and version to ExtensionContext [api, api-proposal, feature-request]- [@eamodio](https://github.com/eamodio \"Issue 116906 is assigned to eamodio\")\n\n- [#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-finalization, api-proposal, custom-editors, feature-request, 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, ghcs-in-progress, remote-explorer]\n- [#114898](https://github.com/microsoft/vscode/issues/114898 \"[ext-api] provide Pseudoterminal.onDidChangeName event\") [ext-api] provide Pseudoterminal.onDidChangeName event [api, api-proposal, feature-request, help wanted]- [@Tyriar](https://github.com/Tyriar \"Issue 114898 is assigned to Tyriar\")\n\n- [#111521](https://github.com/microsoft/vscode/issues/111521 \"Support a way to have nightly/insiders versions of extensions not activate if \"main\" extension is installed/enabled\") Support a way to have nightly/insiders versions of extensions not activate if \"main\" extension is installed/enabled [api, api-proposal, extensions, extensions-development, feature-request, marketplace]- [@sandy081](https://github.com/sandy081 \"Issue 111521 is assigned to sandy081\")\n\n- [#110267](https://github.com/microsoft/vscode/issues/110267 \"Pass telemetry enablement to extensions\") Pass telemetry enablement to extensions [api-proposal, feature-request, telemetry]- [@sbatten](https://github.com/sbatten \"Issue 110267 is assigned to sbatten\")\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, testing, 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, on-testplan]- [@weinand](https://github.com/weinand \"Issue 105690 is assigned to weinand\")\n\n" + "value": "- [#119097](https://github.com/microsoft/vscode/issues/119097 \"Allow extensions to contribute getting started content\") Allow extensions to contribute getting started content [api, api-proposal, feature-request, getting-started]- [@JacksonKearl](https://github.com/JacksonKearl \"Issue 119097 is assigned to JacksonKearl\")\n\n- [#118084](https://github.com/microsoft/vscode/issues/118084 \"Add trigger reason to code actions\") Add trigger reason to code actions [api, api-proposal, editor-code-actions]- [@mjbvz](https://github.com/mjbvz \"Issue 118084 is assigned to mjbvz\")\n\n- [#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-finalization, api-proposal, custom-editors, feature-request, 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, ghcs-in-progress, remote-explorer]\n- [#114898](https://github.com/microsoft/vscode/issues/114898 \"[ext-api] provide Pseudoterminal.onDidChangeName event\") [ext-api] provide Pseudoterminal.onDidChangeName event [api, api-proposal, feature-request, help wanted]- [@Tyriar](https://github.com/Tyriar \"Issue 114898 is assigned to Tyriar\")\n\n- [#111521](https://github.com/microsoft/vscode/issues/111521 \"Support a way to have nightly/insiders versions of extensions not activate if \"main\" extension is installed/enabled\") Support a way to have nightly/insiders versions of extensions not activate if \"main\" extension is installed/enabled [api, api-proposal, extensions, extensions-development, feature-request, marketplace]- [@sandy081](https://github.com/sandy081 \"Issue 111521 is assigned to sandy081\")\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, testing, under-discussion]- [@connor4312](https://github.com/connor4312 \"Issue 107467 is assigned to connor4312\")\n\n- [#77423](https://github.com/microsoft/vscode/issues/77423 \"Add title property to QuickPickOptions (showQuickPick)\") Add title property to QuickPickOptions (showQuickPick) [api-proposal, feature-request, quick-pick]- [@TylerLeonhardt](https://github.com/TylerLeonhardt \"Issue 77423 is assigned to TylerLeonhardt\")\n\n" } ] } diff --git a/README.md b/README.md index eb792510e1c..0a9a62d9b7f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## The Repository -This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Studio Code](https://code.visualstudio.com) product. Not only do we work on code and issues here, we also publish our [roadmap](https://github.com/microsoft/vscode/wiki/Roadmap), [monthly iteration plans](https://github.com/microsoft/vscode/wiki/Iteration-Plans), and our [endgame plans](https://github.com/microsoft/vscode/wiki/Running-the-Endgame). This source code is available to everyone under the standard [MIT license](https://github.com/microsoft/vscode/blob/main/LICENSE.txt). +This repository ("`Code - OSS`") is where we (Microsoft) develop the [Visual Studio Code](https://code.visualstudio.com) product together with the community. Not only do we work on code and issues here, we also publish our [roadmap](https://github.com/microsoft/vscode/wiki/Roadmap), [monthly iteration plans](https://github.com/microsoft/vscode/wiki/Iteration-Plans), and our [endgame plans](https://github.com/microsoft/vscode/wiki/Running-the-Endgame). This source code is available to everyone under the standard [MIT license](https://github.com/microsoft/vscode/blob/main/LICENSE.txt). ## Visual Studio Code diff --git a/build/filters.js b/build/filters.js index f0270742e0f..8c0b94dc3f8 100644 --- a/build/filters.js +++ b/build/filters.js @@ -51,6 +51,7 @@ module.exports.indentationFilter = [ '!test/monaco/out/**', '!test/smoke/out/**', '!extensions/typescript-language-features/test-workspace/**', + '!extensions/notebook-markdown-extensions/notebook-out/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', '!extensions/vscode-custom-editor-tests/test-workspace/**', @@ -118,6 +119,7 @@ module.exports.copyrightFilter = [ '!resources/completions/**', '!extensions/configuration-editing/build/inline-allOf.ts', '!extensions/markdown-language-features/media/highlight.css', + '!extensions/notebook-markdown-extensions/notebook-out/**', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', '!src/vs/editor/test/node/classification/typescript-test.ts', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index e3ca3514bb6..a06f3f63d89 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -58,9 +58,9 @@ const vscodeResources = [ 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', - 'out-build/vs/base/node/userDataPath.js', 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', + 'out-build/vs/platform/environment/node/userDataPath.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/build/lib/electron.js b/build/lib/electron.js index 42b7c2f8126..a7e397e7f86 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -28,7 +28,7 @@ exports.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/lib/layersChecker.js b/build/lib/layersChecker.js index 9ef9dc5914d..147d038f50b 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -56,6 +56,7 @@ const CORE_TYPES = [ const NATIVE_TYPES = [ 'NativeParsedArgs', 'INativeEnvironmentService', + 'AbstractNativeEnvironmentService', 'INativeWindowConfiguration', 'ICommonNativeHostService' ]; @@ -80,19 +81,9 @@ const RULES = [ '@types/node' // no node.js ] }, - // Common: vs/platform/environment/common/argv.ts + // Common: vs/platform/environment/common/* { - target: '**/vs/platform/environment/common/argv.ts', - disallowedTypes: [ /* Ignore native types that are defined from here */], - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', - '@types/node' // no node.js - ] - }, - // Common: vs/platform/environment/common/environment.ts - { - target: '**/vs/platform/environment/common/environment.ts', + target: '**/vs/platform/environment/common/*.ts', disallowedTypes: [ /* Ignore native types that are defined from here */], allowedTypes: CORE_TYPES, disallowedDefinitions: [ diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index c0d67db6017..5b8e7fc667b 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -58,6 +58,7 @@ const CORE_TYPES = [ const NATIVE_TYPES = [ 'NativeParsedArgs', 'INativeEnvironmentService', + 'AbstractNativeEnvironmentService', 'INativeWindowConfiguration', 'ICommonNativeHostService' ]; @@ -87,20 +88,9 @@ const RULES = [ ] }, - // Common: vs/platform/environment/common/argv.ts + // Common: vs/platform/environment/common/* { - target: '**/vs/platform/environment/common/argv.ts', - disallowedTypes: [/* Ignore native types that are defined from here */], - allowedTypes: CORE_TYPES, - disallowedDefinitions: [ - 'lib.dom.d.ts', // no DOM - '@types/node' // no node.js - ] - }, - - // Common: vs/platform/environment/common/environment.ts - { - target: '**/vs/platform/environment/common/environment.ts', + target: '**/vs/platform/environment/common/*.ts', disallowedTypes: [/* Ignore native types that are defined from here */], allowedTypes: CORE_TYPES, disallowedDefinitions: [ diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index d7846aad40e..e52d8a12c8e 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -22,9 +22,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -53,27 +53,57 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" } } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": ["notify", "openBrowser", "openPreview", "silent", "ignore"], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [{ "body": { "onAutoForward": "ignore" } }], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "settings": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json index 91f9249af80..5115e778e79 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.generated.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.generated.json @@ -130,9 +130,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -161,28 +161,70 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" }, "additionalProperties": false } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { @@ -387,9 +429,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -418,28 +460,70 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" }, "additionalProperties": false } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { @@ -620,9 +704,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -651,28 +735,70 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" }, "additionalProperties": false } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { @@ -819,9 +945,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -850,28 +976,70 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" }, "additionalProperties": false } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { @@ -987,9 +1155,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -1018,28 +1186,70 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" }, "additionalProperties": false } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.src.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json index bac86dc8af5..baef1756a64 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.src.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.src.json @@ -36,9 +36,9 @@ "portsAttributes": { "type": "object", "patternProperties": { - "^\\d+(\\-\\d+)?$": { + "(^\\d+(\\-\\d+)?$)|(.+)": { "type": "object", - "description": "A port, or range of ports (ex. \"40000-55000\") that the attributes should apply to", + "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", "properties": { "onAutoForward": { "type": "string", @@ -67,27 +67,69 @@ "label": { "type": "string", "description": "Label that will be shown in the UI for this port.", - "default": "Labeled Port" + "default": "Application" } }, "default": { - "label": "Labeled Port", + "label": "Application", "onAutoForward": "notify" } } }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```", + "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", "defaultSnippets": [ { "body": { "${1:3000}": { - "label": "${2:My Port}", + "label": "${2:Application}", "onAutoForward": "notify" } } } ], - "errorMessage": "Must be a port number or a range of port numbers", + "additionalProperties": false + }, + "unconfiguredPortsAttributes": { + "type": "object", + "properties": { + "onAutoForward": { + "type": "string", + "enum": [ + "notify", + "openBrowser", + "openPreview", + "silent", + "ignore" + ], + "enumDescriptions": [ + "Shows a notification when a port is automatically forwarded.", + "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", + "Opens a preview in the same window when the port is automatically forwarded.", + "Shows no notification and takes no action when this port is automatically forwarded.", + "This port will not be automatically forwarded." + ], + "description": "Defines the action that occurs when the port is discovered for automatic forwarding", + "default": "notify" + }, + "elevateIfNeeded": { + "type": "boolean", + "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", + "default": false + }, + "label": { + "type": "string", + "description": "Label that will be shown in the UI for this port.", + "default": "Application" + } + }, + "defaultSnippets": [ + { + "body": { + "onAutoForward": "ignore" + } + } + ], + "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", "additionalProperties": false }, "remoteEnv": { diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index f78b3af40c4..a8c7f464cbd 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -67,7 +67,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi const lsDoc = toLSTextDocument(document); position = document.validatePosition(position); - if (document.languageId === 'html') { + if (syntax === 'html') { if (context.triggerKind === vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) { switch (this.lastCompletionType) { case 'html': diff --git a/extensions/markdown-language-features/notebook-out/index.js b/extensions/markdown-language-features/notebook-out/index.js index ef0a3821273..66df08f4026 100644 --- a/extensions/markdown-language-features/notebook-out/index.js +++ b/extensions/markdown-language-features/notebook-out/index.js @@ -1 +1 @@ -!function(e){var t={};function r(n){if(t[n])return t[n].exports;var s=t[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.l=!0,s.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)r.d(n,s,function(t){return e[t]}.bind(null,s));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=9)}([function(e,t,r){"use strict";var n=Object.prototype.hasOwnProperty;function s(e,t){return n.call(e,t)}function o(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function i(e){if(e>65535){var t=55296+((e-=65536)>>10),r=56320+(1023&e);return String.fromCharCode(t,r)}return String.fromCharCode(e)}var a=/\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g,u=new RegExp(a.source+"|"+/&([a-z#][a-z0-9]{1,31});/gi.source,"gi"),c=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,l=r(3);var p=/[&<>"]/,h=/[&<>"]/g,f={"&":"&","<":"<",">":">",'"':"""};function d(e){return f[e]}var m=/[.?*+^$[\]\\(){}|-]/g;var g=r(4);t.lib={},t.lib.mdurl=r(5),t.lib.ucmicro=r(17),t.assign=function(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach((function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach((function(r){e[r]=t[r]}))}})),e},t.isString=function(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)},t.has=s,t.unescapeMd=function(e){return e.indexOf("\\")<0?e:e.replace(a,"$1")},t.unescapeAll=function(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(u,(function(e,t,r){return t||function(e,t){var r=0;return s(l,t)?l[t]:35===t.charCodeAt(0)&&c.test(t)&&o(r="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?i(r):e}(e,r)}))},t.isValidEntityCode=o,t.fromCodePoint=i,t.escapeHtml=function(e){return p.test(e)?e.replace(h,d):e},t.arrayReplaceAt=function(e,t,r){return[].concat(e.slice(0,t),r,e.slice(t+1))},t.isSpace=function(e){switch(e){case 9:case 32:return!0}return!1},t.isWhiteSpace=function(e){if(e>=8192&&e<=8202)return!0;switch(e){case 9:case 10:case 11:case 12:case 13:case 32:case 160:case 5760:case 8239:case 8287:case 12288:return!0}return!1},t.isMdAsciiPunct=function(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 124:case 125:case 126:return!0;default:return!1}},t.isPunctChar=function(e){return g.test(e)},t.escapeRE=function(e){return e.replace(m,"\\$&")},t.normalizeReference=function(e){return e=e.trim().replace(/\s+/g," "),"Ṿ"==="ẞ".toLowerCase()&&(e=e.replace(/ẞ/g,"ß")),e.toLowerCase().toUpperCase()}},function(e,t,r){"use strict";function n(){this.__rules__=[],this.__cache__=null}n.prototype.__find__=function(e){for(var t=0;t=0&&(r=this.attrs[t][1]),r},n.prototype.attrJoin=function(e,t){var r=this.attrIndex(e);r<0?this.attrPush([e,t]):this.attrs[r][1]=this.attrs[r][1]+" "+t},e.exports=n},function(e,t,r){"use strict";e.exports=r(12)},function(e,t){e.exports=/[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E49\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/},function(e,t,r){"use strict";e.exports.encode=r(13),e.exports.decode=r(14),e.exports.format=r(15),e.exports.parse=r(16)},function(e,t,r){"use strict";var n="<[A-Za-z][A-Za-z0-9\\-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*\\/?>",s="<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>",o=new RegExp("^(?:"+n+"|"+s+"|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|<[?][\\s\\S]*?[?]>|]*>|)"),i=new RegExp("^(?:"+n+"|"+s+")");e.exports.HTML_TAG_RE=o,e.exports.HTML_OPEN_CLOSE_TAG_RE=i},function(e,t,r){"use strict";function n(e,t){var r,n,s,o,i,a=[],u=t.length;for(r=0;r=0;r--)95!==(n=t[r]).marker&&42!==n.marker||-1!==n.end&&(s=t[n.end],a=r>0&&t[r-1].end===n.end+1&&t[r-1].token===n.token-1&&t[n.end+1].token===s.token+1&&t[r-1].marker===n.marker,i=String.fromCharCode(n.marker),(o=e.tokens[n.token]).type=a?"strong_open":"em_open",o.tag=a?"strong":"em",o.nesting=1,o.markup=a?i+i:i,o.content="",(o=e.tokens[s.token]).type=a?"strong_close":"em_close",o.tag=a?"strong":"em",o.nesting=-1,o.markup=a?i+i:i,o.content="",a&&(e.tokens[t[r-1].token].content="",e.tokens[t[n.end+1].token].content="",r--))}e.exports.tokenize=function(e,t){var r,n,s=e.pos,o=e.src.charCodeAt(s);if(t)return!1;if(95!==o&&42!==o)return!1;for(n=e.scanDelims(e.pos,42===o),r=0;r{t(e)};acquireNotebookRendererApi("notebookCoreTestRenderer").onDidCreateMarkdown(({element:t,content:r})=>{const n=e.render(r);t.innerHTML=n});const t=new Date;console.log(`${t.getSeconds()}:${t.getMilliseconds().toString().padStart(3,"0")}`,"loaded markdown it")}()},function(e,t,r){"use strict";e.exports=r(11)},function(e,t,r){"use strict";var n=r(0),s=r(22),o=r(26),i=r(27),a=r(35),u=r(49),c=r(62),l=r(5),p=r(68),h={default:r(71),zero:r(72),commonmark:r(73)},f=/^(vbscript|javascript|file|data):/,d=/^data:image\/(gif|png|jpeg|webp);/;function m(e){var t=e.trim().toLowerCase();return!f.test(t)||!!d.test(t)}var g=["http:","https:","mailto:"];function _(e){var t=l.parse(e,!0);if(t.hostname&&(!t.protocol||g.indexOf(t.protocol)>=0))try{t.hostname=p.toASCII(t.hostname)}catch(e){}return l.encode(l.format(t))}function b(e){var t=l.parse(e,!0);if(t.hostname&&(!t.protocol||g.indexOf(t.protocol)>=0))try{t.hostname=p.toUnicode(t.hostname)}catch(e){}return l.decode(l.format(t),l.decode.defaultChars+"%")}function k(e,t){if(!(this instanceof k))return new k(e,t);t||n.isString(e)||(t=e||{},e="default"),this.inline=new u,this.block=new a,this.core=new i,this.renderer=new o,this.linkify=new c,this.validateLink=m,this.normalizeLink=_,this.normalizeLinkText=b,this.utils=n,this.helpers=n.assign({},s),this.options={},this.configure(e),t&&this.set(t)}k.prototype.set=function(e){return n.assign(this.options,e),this},k.prototype.configure=function(e){var t,r=this;if(n.isString(e)&&!(e=h[t=e]))throw new Error('Wrong `markdown-it` preset "'+t+'", check name');if(!e)throw new Error("Wrong `markdown-it` preset, can't be empty");return e.options&&r.set(e.options),e.components&&Object.keys(e.components).forEach((function(t){e.components[t].rules&&r[t].ruler.enableOnly(e.components[t].rules),e.components[t].rules2&&r[t].ruler2.enableOnly(e.components[t].rules2)})),this},k.prototype.enable=function(e,t){var r=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){r=r.concat(this[t].ruler.enable(e,!0))}),this),r=r.concat(this.inline.ruler2.enable(e,!0));var n=e.filter((function(e){return r.indexOf(e)<0}));if(n.length&&!t)throw new Error("MarkdownIt. Failed to enable unknown rule(s): "+n);return this},k.prototype.disable=function(e,t){var r=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){r=r.concat(this[t].ruler.disable(e,!0))}),this),r=r.concat(this.inline.ruler2.disable(e,!0));var n=e.filter((function(e){return r.indexOf(e)<0}));if(n.length&&!t)throw new Error("MarkdownIt. Failed to disable unknown rule(s): "+n);return this},k.prototype.use=function(e){var t=[this].concat(Array.prototype.slice.call(arguments,1));return e.apply(e,t),this},k.prototype.parse=function(e,t){if("string"!=typeof e)throw new Error("Input data should be a String");var r=new this.core.State(e,this,t);return this.core.process(r),r.tokens},k.prototype.render=function(e,t){return t=t||{},this.renderer.render(this.parse(e,t),this.options,t)},k.prototype.parseInline=function(e,t){var r=new this.core.State(e,this,t);return r.inlineMode=!0,this.core.process(r),r.tokens},k.prototype.renderInline=function(e,t){return t=t||{},this.renderer.render(this.parseInline(e,t),this.options,t)},e.exports=k},function(e){e.exports=JSON.parse('{"Aacute":"Á","aacute":"á","Abreve":"Ă","abreve":"ă","ac":"∾","acd":"∿","acE":"∾̳","Acirc":"Â","acirc":"â","acute":"´","Acy":"А","acy":"а","AElig":"Æ","aelig":"æ","af":"⁡","Afr":"𝔄","afr":"𝔞","Agrave":"À","agrave":"à","alefsym":"ℵ","aleph":"ℵ","Alpha":"Α","alpha":"α","Amacr":"Ā","amacr":"ā","amalg":"⨿","amp":"&","AMP":"&","andand":"⩕","And":"⩓","and":"∧","andd":"⩜","andslope":"⩘","andv":"⩚","ang":"∠","ange":"⦤","angle":"∠","angmsdaa":"⦨","angmsdab":"⦩","angmsdac":"⦪","angmsdad":"⦫","angmsdae":"⦬","angmsdaf":"⦭","angmsdag":"⦮","angmsdah":"⦯","angmsd":"∡","angrt":"∟","angrtvb":"⊾","angrtvbd":"⦝","angsph":"∢","angst":"Å","angzarr":"⍼","Aogon":"Ą","aogon":"ą","Aopf":"𝔸","aopf":"𝕒","apacir":"⩯","ap":"≈","apE":"⩰","ape":"≊","apid":"≋","apos":"\'","ApplyFunction":"⁡","approx":"≈","approxeq":"≊","Aring":"Å","aring":"å","Ascr":"𝒜","ascr":"𝒶","Assign":"≔","ast":"*","asymp":"≈","asympeq":"≍","Atilde":"Ã","atilde":"ã","Auml":"Ä","auml":"ä","awconint":"∳","awint":"⨑","backcong":"≌","backepsilon":"϶","backprime":"‵","backsim":"∽","backsimeq":"⋍","Backslash":"∖","Barv":"⫧","barvee":"⊽","barwed":"⌅","Barwed":"⌆","barwedge":"⌅","bbrk":"⎵","bbrktbrk":"⎶","bcong":"≌","Bcy":"Б","bcy":"б","bdquo":"„","becaus":"∵","because":"∵","Because":"∵","bemptyv":"⦰","bepsi":"϶","bernou":"ℬ","Bernoullis":"ℬ","Beta":"Β","beta":"β","beth":"ℶ","between":"≬","Bfr":"𝔅","bfr":"𝔟","bigcap":"⋂","bigcirc":"◯","bigcup":"⋃","bigodot":"⨀","bigoplus":"⨁","bigotimes":"⨂","bigsqcup":"⨆","bigstar":"★","bigtriangledown":"▽","bigtriangleup":"△","biguplus":"⨄","bigvee":"⋁","bigwedge":"⋀","bkarow":"⤍","blacklozenge":"⧫","blacksquare":"▪","blacktriangle":"▴","blacktriangledown":"▾","blacktriangleleft":"◂","blacktriangleright":"▸","blank":"␣","blk12":"▒","blk14":"░","blk34":"▓","block":"█","bne":"=⃥","bnequiv":"≡⃥","bNot":"⫭","bnot":"⌐","Bopf":"𝔹","bopf":"𝕓","bot":"⊥","bottom":"⊥","bowtie":"⋈","boxbox":"⧉","boxdl":"┐","boxdL":"╕","boxDl":"╖","boxDL":"╗","boxdr":"┌","boxdR":"╒","boxDr":"╓","boxDR":"╔","boxh":"─","boxH":"═","boxhd":"┬","boxHd":"╤","boxhD":"╥","boxHD":"╦","boxhu":"┴","boxHu":"╧","boxhU":"╨","boxHU":"╩","boxminus":"⊟","boxplus":"⊞","boxtimes":"⊠","boxul":"┘","boxuL":"╛","boxUl":"╜","boxUL":"╝","boxur":"└","boxuR":"╘","boxUr":"╙","boxUR":"╚","boxv":"│","boxV":"║","boxvh":"┼","boxvH":"╪","boxVh":"╫","boxVH":"╬","boxvl":"┤","boxvL":"╡","boxVl":"╢","boxVL":"╣","boxvr":"├","boxvR":"╞","boxVr":"╟","boxVR":"╠","bprime":"‵","breve":"˘","Breve":"˘","brvbar":"¦","bscr":"𝒷","Bscr":"ℬ","bsemi":"⁏","bsim":"∽","bsime":"⋍","bsolb":"⧅","bsol":"\\\\","bsolhsub":"⟈","bull":"•","bullet":"•","bump":"≎","bumpE":"⪮","bumpe":"≏","Bumpeq":"≎","bumpeq":"≏","Cacute":"Ć","cacute":"ć","capand":"⩄","capbrcup":"⩉","capcap":"⩋","cap":"∩","Cap":"⋒","capcup":"⩇","capdot":"⩀","CapitalDifferentialD":"ⅅ","caps":"∩︀","caret":"⁁","caron":"ˇ","Cayleys":"ℭ","ccaps":"⩍","Ccaron":"Č","ccaron":"č","Ccedil":"Ç","ccedil":"ç","Ccirc":"Ĉ","ccirc":"ĉ","Cconint":"∰","ccups":"⩌","ccupssm":"⩐","Cdot":"Ċ","cdot":"ċ","cedil":"¸","Cedilla":"¸","cemptyv":"⦲","cent":"¢","centerdot":"·","CenterDot":"·","cfr":"𝔠","Cfr":"ℭ","CHcy":"Ч","chcy":"ч","check":"✓","checkmark":"✓","Chi":"Χ","chi":"χ","circ":"ˆ","circeq":"≗","circlearrowleft":"↺","circlearrowright":"↻","circledast":"⊛","circledcirc":"⊚","circleddash":"⊝","CircleDot":"⊙","circledR":"®","circledS":"Ⓢ","CircleMinus":"⊖","CirclePlus":"⊕","CircleTimes":"⊗","cir":"○","cirE":"⧃","cire":"≗","cirfnint":"⨐","cirmid":"⫯","cirscir":"⧂","ClockwiseContourIntegral":"∲","CloseCurlyDoubleQuote":"”","CloseCurlyQuote":"’","clubs":"♣","clubsuit":"♣","colon":":","Colon":"∷","Colone":"⩴","colone":"≔","coloneq":"≔","comma":",","commat":"@","comp":"∁","compfn":"∘","complement":"∁","complexes":"ℂ","cong":"≅","congdot":"⩭","Congruent":"≡","conint":"∮","Conint":"∯","ContourIntegral":"∮","copf":"𝕔","Copf":"ℂ","coprod":"∐","Coproduct":"∐","copy":"©","COPY":"©","copysr":"℗","CounterClockwiseContourIntegral":"∳","crarr":"↵","cross":"✗","Cross":"⨯","Cscr":"𝒞","cscr":"𝒸","csub":"⫏","csube":"⫑","csup":"⫐","csupe":"⫒","ctdot":"⋯","cudarrl":"⤸","cudarrr":"⤵","cuepr":"⋞","cuesc":"⋟","cularr":"↶","cularrp":"⤽","cupbrcap":"⩈","cupcap":"⩆","CupCap":"≍","cup":"∪","Cup":"⋓","cupcup":"⩊","cupdot":"⊍","cupor":"⩅","cups":"∪︀","curarr":"↷","curarrm":"⤼","curlyeqprec":"⋞","curlyeqsucc":"⋟","curlyvee":"⋎","curlywedge":"⋏","curren":"¤","curvearrowleft":"↶","curvearrowright":"↷","cuvee":"⋎","cuwed":"⋏","cwconint":"∲","cwint":"∱","cylcty":"⌭","dagger":"†","Dagger":"‡","daleth":"ℸ","darr":"↓","Darr":"↡","dArr":"⇓","dash":"‐","Dashv":"⫤","dashv":"⊣","dbkarow":"⤏","dblac":"˝","Dcaron":"Ď","dcaron":"ď","Dcy":"Д","dcy":"д","ddagger":"‡","ddarr":"⇊","DD":"ⅅ","dd":"ⅆ","DDotrahd":"⤑","ddotseq":"⩷","deg":"°","Del":"∇","Delta":"Δ","delta":"δ","demptyv":"⦱","dfisht":"⥿","Dfr":"𝔇","dfr":"𝔡","dHar":"⥥","dharl":"⇃","dharr":"⇂","DiacriticalAcute":"´","DiacriticalDot":"˙","DiacriticalDoubleAcute":"˝","DiacriticalGrave":"`","DiacriticalTilde":"˜","diam":"⋄","diamond":"⋄","Diamond":"⋄","diamondsuit":"♦","diams":"♦","die":"¨","DifferentialD":"ⅆ","digamma":"ϝ","disin":"⋲","div":"÷","divide":"÷","divideontimes":"⋇","divonx":"⋇","DJcy":"Ђ","djcy":"ђ","dlcorn":"⌞","dlcrop":"⌍","dollar":"$","Dopf":"𝔻","dopf":"𝕕","Dot":"¨","dot":"˙","DotDot":"⃜","doteq":"≐","doteqdot":"≑","DotEqual":"≐","dotminus":"∸","dotplus":"∔","dotsquare":"⊡","doublebarwedge":"⌆","DoubleContourIntegral":"∯","DoubleDot":"¨","DoubleDownArrow":"⇓","DoubleLeftArrow":"⇐","DoubleLeftRightArrow":"⇔","DoubleLeftTee":"⫤","DoubleLongLeftArrow":"⟸","DoubleLongLeftRightArrow":"⟺","DoubleLongRightArrow":"⟹","DoubleRightArrow":"⇒","DoubleRightTee":"⊨","DoubleUpArrow":"⇑","DoubleUpDownArrow":"⇕","DoubleVerticalBar":"∥","DownArrowBar":"⤓","downarrow":"↓","DownArrow":"↓","Downarrow":"⇓","DownArrowUpArrow":"⇵","DownBreve":"̑","downdownarrows":"⇊","downharpoonleft":"⇃","downharpoonright":"⇂","DownLeftRightVector":"⥐","DownLeftTeeVector":"⥞","DownLeftVectorBar":"⥖","DownLeftVector":"↽","DownRightTeeVector":"⥟","DownRightVectorBar":"⥗","DownRightVector":"⇁","DownTeeArrow":"↧","DownTee":"⊤","drbkarow":"⤐","drcorn":"⌟","drcrop":"⌌","Dscr":"𝒟","dscr":"𝒹","DScy":"Ѕ","dscy":"ѕ","dsol":"⧶","Dstrok":"Đ","dstrok":"đ","dtdot":"⋱","dtri":"▿","dtrif":"▾","duarr":"⇵","duhar":"⥯","dwangle":"⦦","DZcy":"Џ","dzcy":"џ","dzigrarr":"⟿","Eacute":"É","eacute":"é","easter":"⩮","Ecaron":"Ě","ecaron":"ě","Ecirc":"Ê","ecirc":"ê","ecir":"≖","ecolon":"≕","Ecy":"Э","ecy":"э","eDDot":"⩷","Edot":"Ė","edot":"ė","eDot":"≑","ee":"ⅇ","efDot":"≒","Efr":"𝔈","efr":"𝔢","eg":"⪚","Egrave":"È","egrave":"è","egs":"⪖","egsdot":"⪘","el":"⪙","Element":"∈","elinters":"⏧","ell":"ℓ","els":"⪕","elsdot":"⪗","Emacr":"Ē","emacr":"ē","empty":"∅","emptyset":"∅","EmptySmallSquare":"◻","emptyv":"∅","EmptyVerySmallSquare":"▫","emsp13":" ","emsp14":" ","emsp":" ","ENG":"Ŋ","eng":"ŋ","ensp":" ","Eogon":"Ę","eogon":"ę","Eopf":"𝔼","eopf":"𝕖","epar":"⋕","eparsl":"⧣","eplus":"⩱","epsi":"ε","Epsilon":"Ε","epsilon":"ε","epsiv":"ϵ","eqcirc":"≖","eqcolon":"≕","eqsim":"≂","eqslantgtr":"⪖","eqslantless":"⪕","Equal":"⩵","equals":"=","EqualTilde":"≂","equest":"≟","Equilibrium":"⇌","equiv":"≡","equivDD":"⩸","eqvparsl":"⧥","erarr":"⥱","erDot":"≓","escr":"ℯ","Escr":"ℰ","esdot":"≐","Esim":"⩳","esim":"≂","Eta":"Η","eta":"η","ETH":"Ð","eth":"ð","Euml":"Ë","euml":"ë","euro":"€","excl":"!","exist":"∃","Exists":"∃","expectation":"ℰ","exponentiale":"ⅇ","ExponentialE":"ⅇ","fallingdotseq":"≒","Fcy":"Ф","fcy":"ф","female":"♀","ffilig":"ffi","fflig":"ff","ffllig":"ffl","Ffr":"𝔉","ffr":"𝔣","filig":"fi","FilledSmallSquare":"◼","FilledVerySmallSquare":"▪","fjlig":"fj","flat":"♭","fllig":"fl","fltns":"▱","fnof":"ƒ","Fopf":"𝔽","fopf":"𝕗","forall":"∀","ForAll":"∀","fork":"⋔","forkv":"⫙","Fouriertrf":"ℱ","fpartint":"⨍","frac12":"½","frac13":"⅓","frac14":"¼","frac15":"⅕","frac16":"⅙","frac18":"⅛","frac23":"⅔","frac25":"⅖","frac34":"¾","frac35":"⅗","frac38":"⅜","frac45":"⅘","frac56":"⅚","frac58":"⅝","frac78":"⅞","frasl":"⁄","frown":"⌢","fscr":"𝒻","Fscr":"ℱ","gacute":"ǵ","Gamma":"Γ","gamma":"γ","Gammad":"Ϝ","gammad":"ϝ","gap":"⪆","Gbreve":"Ğ","gbreve":"ğ","Gcedil":"Ģ","Gcirc":"Ĝ","gcirc":"ĝ","Gcy":"Г","gcy":"г","Gdot":"Ġ","gdot":"ġ","ge":"≥","gE":"≧","gEl":"⪌","gel":"⋛","geq":"≥","geqq":"≧","geqslant":"⩾","gescc":"⪩","ges":"⩾","gesdot":"⪀","gesdoto":"⪂","gesdotol":"⪄","gesl":"⋛︀","gesles":"⪔","Gfr":"𝔊","gfr":"𝔤","gg":"≫","Gg":"⋙","ggg":"⋙","gimel":"ℷ","GJcy":"Ѓ","gjcy":"ѓ","gla":"⪥","gl":"≷","glE":"⪒","glj":"⪤","gnap":"⪊","gnapprox":"⪊","gne":"⪈","gnE":"≩","gneq":"⪈","gneqq":"≩","gnsim":"⋧","Gopf":"𝔾","gopf":"𝕘","grave":"`","GreaterEqual":"≥","GreaterEqualLess":"⋛","GreaterFullEqual":"≧","GreaterGreater":"⪢","GreaterLess":"≷","GreaterSlantEqual":"⩾","GreaterTilde":"≳","Gscr":"𝒢","gscr":"ℊ","gsim":"≳","gsime":"⪎","gsiml":"⪐","gtcc":"⪧","gtcir":"⩺","gt":">","GT":">","Gt":"≫","gtdot":"⋗","gtlPar":"⦕","gtquest":"⩼","gtrapprox":"⪆","gtrarr":"⥸","gtrdot":"⋗","gtreqless":"⋛","gtreqqless":"⪌","gtrless":"≷","gtrsim":"≳","gvertneqq":"≩︀","gvnE":"≩︀","Hacek":"ˇ","hairsp":" ","half":"½","hamilt":"ℋ","HARDcy":"Ъ","hardcy":"ъ","harrcir":"⥈","harr":"↔","hArr":"⇔","harrw":"↭","Hat":"^","hbar":"ℏ","Hcirc":"Ĥ","hcirc":"ĥ","hearts":"♥","heartsuit":"♥","hellip":"…","hercon":"⊹","hfr":"𝔥","Hfr":"ℌ","HilbertSpace":"ℋ","hksearow":"⤥","hkswarow":"⤦","hoarr":"⇿","homtht":"∻","hookleftarrow":"↩","hookrightarrow":"↪","hopf":"𝕙","Hopf":"ℍ","horbar":"―","HorizontalLine":"─","hscr":"𝒽","Hscr":"ℋ","hslash":"ℏ","Hstrok":"Ħ","hstrok":"ħ","HumpDownHump":"≎","HumpEqual":"≏","hybull":"⁃","hyphen":"‐","Iacute":"Í","iacute":"í","ic":"⁣","Icirc":"Î","icirc":"î","Icy":"И","icy":"и","Idot":"İ","IEcy":"Е","iecy":"е","iexcl":"¡","iff":"⇔","ifr":"𝔦","Ifr":"ℑ","Igrave":"Ì","igrave":"ì","ii":"ⅈ","iiiint":"⨌","iiint":"∭","iinfin":"⧜","iiota":"℩","IJlig":"IJ","ijlig":"ij","Imacr":"Ī","imacr":"ī","image":"ℑ","ImaginaryI":"ⅈ","imagline":"ℐ","imagpart":"ℑ","imath":"ı","Im":"ℑ","imof":"⊷","imped":"Ƶ","Implies":"⇒","incare":"℅","in":"∈","infin":"∞","infintie":"⧝","inodot":"ı","intcal":"⊺","int":"∫","Int":"∬","integers":"ℤ","Integral":"∫","intercal":"⊺","Intersection":"⋂","intlarhk":"⨗","intprod":"⨼","InvisibleComma":"⁣","InvisibleTimes":"⁢","IOcy":"Ё","iocy":"ё","Iogon":"Į","iogon":"į","Iopf":"𝕀","iopf":"𝕚","Iota":"Ι","iota":"ι","iprod":"⨼","iquest":"¿","iscr":"𝒾","Iscr":"ℐ","isin":"∈","isindot":"⋵","isinE":"⋹","isins":"⋴","isinsv":"⋳","isinv":"∈","it":"⁢","Itilde":"Ĩ","itilde":"ĩ","Iukcy":"І","iukcy":"і","Iuml":"Ï","iuml":"ï","Jcirc":"Ĵ","jcirc":"ĵ","Jcy":"Й","jcy":"й","Jfr":"𝔍","jfr":"𝔧","jmath":"ȷ","Jopf":"𝕁","jopf":"𝕛","Jscr":"𝒥","jscr":"𝒿","Jsercy":"Ј","jsercy":"ј","Jukcy":"Є","jukcy":"є","Kappa":"Κ","kappa":"κ","kappav":"ϰ","Kcedil":"Ķ","kcedil":"ķ","Kcy":"К","kcy":"к","Kfr":"𝔎","kfr":"𝔨","kgreen":"ĸ","KHcy":"Х","khcy":"х","KJcy":"Ќ","kjcy":"ќ","Kopf":"𝕂","kopf":"𝕜","Kscr":"𝒦","kscr":"𝓀","lAarr":"⇚","Lacute":"Ĺ","lacute":"ĺ","laemptyv":"⦴","lagran":"ℒ","Lambda":"Λ","lambda":"λ","lang":"⟨","Lang":"⟪","langd":"⦑","langle":"⟨","lap":"⪅","Laplacetrf":"ℒ","laquo":"«","larrb":"⇤","larrbfs":"⤟","larr":"←","Larr":"↞","lArr":"⇐","larrfs":"⤝","larrhk":"↩","larrlp":"↫","larrpl":"⤹","larrsim":"⥳","larrtl":"↢","latail":"⤙","lAtail":"⤛","lat":"⪫","late":"⪭","lates":"⪭︀","lbarr":"⤌","lBarr":"⤎","lbbrk":"❲","lbrace":"{","lbrack":"[","lbrke":"⦋","lbrksld":"⦏","lbrkslu":"⦍","Lcaron":"Ľ","lcaron":"ľ","Lcedil":"Ļ","lcedil":"ļ","lceil":"⌈","lcub":"{","Lcy":"Л","lcy":"л","ldca":"⤶","ldquo":"“","ldquor":"„","ldrdhar":"⥧","ldrushar":"⥋","ldsh":"↲","le":"≤","lE":"≦","LeftAngleBracket":"⟨","LeftArrowBar":"⇤","leftarrow":"←","LeftArrow":"←","Leftarrow":"⇐","LeftArrowRightArrow":"⇆","leftarrowtail":"↢","LeftCeiling":"⌈","LeftDoubleBracket":"⟦","LeftDownTeeVector":"⥡","LeftDownVectorBar":"⥙","LeftDownVector":"⇃","LeftFloor":"⌊","leftharpoondown":"↽","leftharpoonup":"↼","leftleftarrows":"⇇","leftrightarrow":"↔","LeftRightArrow":"↔","Leftrightarrow":"⇔","leftrightarrows":"⇆","leftrightharpoons":"⇋","leftrightsquigarrow":"↭","LeftRightVector":"⥎","LeftTeeArrow":"↤","LeftTee":"⊣","LeftTeeVector":"⥚","leftthreetimes":"⋋","LeftTriangleBar":"⧏","LeftTriangle":"⊲","LeftTriangleEqual":"⊴","LeftUpDownVector":"⥑","LeftUpTeeVector":"⥠","LeftUpVectorBar":"⥘","LeftUpVector":"↿","LeftVectorBar":"⥒","LeftVector":"↼","lEg":"⪋","leg":"⋚","leq":"≤","leqq":"≦","leqslant":"⩽","lescc":"⪨","les":"⩽","lesdot":"⩿","lesdoto":"⪁","lesdotor":"⪃","lesg":"⋚︀","lesges":"⪓","lessapprox":"⪅","lessdot":"⋖","lesseqgtr":"⋚","lesseqqgtr":"⪋","LessEqualGreater":"⋚","LessFullEqual":"≦","LessGreater":"≶","lessgtr":"≶","LessLess":"⪡","lesssim":"≲","LessSlantEqual":"⩽","LessTilde":"≲","lfisht":"⥼","lfloor":"⌊","Lfr":"𝔏","lfr":"𝔩","lg":"≶","lgE":"⪑","lHar":"⥢","lhard":"↽","lharu":"↼","lharul":"⥪","lhblk":"▄","LJcy":"Љ","ljcy":"љ","llarr":"⇇","ll":"≪","Ll":"⋘","llcorner":"⌞","Lleftarrow":"⇚","llhard":"⥫","lltri":"◺","Lmidot":"Ŀ","lmidot":"ŀ","lmoustache":"⎰","lmoust":"⎰","lnap":"⪉","lnapprox":"⪉","lne":"⪇","lnE":"≨","lneq":"⪇","lneqq":"≨","lnsim":"⋦","loang":"⟬","loarr":"⇽","lobrk":"⟦","longleftarrow":"⟵","LongLeftArrow":"⟵","Longleftarrow":"⟸","longleftrightarrow":"⟷","LongLeftRightArrow":"⟷","Longleftrightarrow":"⟺","longmapsto":"⟼","longrightarrow":"⟶","LongRightArrow":"⟶","Longrightarrow":"⟹","looparrowleft":"↫","looparrowright":"↬","lopar":"⦅","Lopf":"𝕃","lopf":"𝕝","loplus":"⨭","lotimes":"⨴","lowast":"∗","lowbar":"_","LowerLeftArrow":"↙","LowerRightArrow":"↘","loz":"◊","lozenge":"◊","lozf":"⧫","lpar":"(","lparlt":"⦓","lrarr":"⇆","lrcorner":"⌟","lrhar":"⇋","lrhard":"⥭","lrm":"‎","lrtri":"⊿","lsaquo":"‹","lscr":"𝓁","Lscr":"ℒ","lsh":"↰","Lsh":"↰","lsim":"≲","lsime":"⪍","lsimg":"⪏","lsqb":"[","lsquo":"‘","lsquor":"‚","Lstrok":"Ł","lstrok":"ł","ltcc":"⪦","ltcir":"⩹","lt":"<","LT":"<","Lt":"≪","ltdot":"⋖","lthree":"⋋","ltimes":"⋉","ltlarr":"⥶","ltquest":"⩻","ltri":"◃","ltrie":"⊴","ltrif":"◂","ltrPar":"⦖","lurdshar":"⥊","luruhar":"⥦","lvertneqq":"≨︀","lvnE":"≨︀","macr":"¯","male":"♂","malt":"✠","maltese":"✠","Map":"⤅","map":"↦","mapsto":"↦","mapstodown":"↧","mapstoleft":"↤","mapstoup":"↥","marker":"▮","mcomma":"⨩","Mcy":"М","mcy":"м","mdash":"—","mDDot":"∺","measuredangle":"∡","MediumSpace":" ","Mellintrf":"ℳ","Mfr":"𝔐","mfr":"𝔪","mho":"℧","micro":"µ","midast":"*","midcir":"⫰","mid":"∣","middot":"·","minusb":"⊟","minus":"−","minusd":"∸","minusdu":"⨪","MinusPlus":"∓","mlcp":"⫛","mldr":"…","mnplus":"∓","models":"⊧","Mopf":"𝕄","mopf":"𝕞","mp":"∓","mscr":"𝓂","Mscr":"ℳ","mstpos":"∾","Mu":"Μ","mu":"μ","multimap":"⊸","mumap":"⊸","nabla":"∇","Nacute":"Ń","nacute":"ń","nang":"∠⃒","nap":"≉","napE":"⩰̸","napid":"≋̸","napos":"ʼn","napprox":"≉","natural":"♮","naturals":"ℕ","natur":"♮","nbsp":" ","nbump":"≎̸","nbumpe":"≏̸","ncap":"⩃","Ncaron":"Ň","ncaron":"ň","Ncedil":"Ņ","ncedil":"ņ","ncong":"≇","ncongdot":"⩭̸","ncup":"⩂","Ncy":"Н","ncy":"н","ndash":"–","nearhk":"⤤","nearr":"↗","neArr":"⇗","nearrow":"↗","ne":"≠","nedot":"≐̸","NegativeMediumSpace":"​","NegativeThickSpace":"​","NegativeThinSpace":"​","NegativeVeryThinSpace":"​","nequiv":"≢","nesear":"⤨","nesim":"≂̸","NestedGreaterGreater":"≫","NestedLessLess":"≪","NewLine":"\\n","nexist":"∄","nexists":"∄","Nfr":"𝔑","nfr":"𝔫","ngE":"≧̸","nge":"≱","ngeq":"≱","ngeqq":"≧̸","ngeqslant":"⩾̸","nges":"⩾̸","nGg":"⋙̸","ngsim":"≵","nGt":"≫⃒","ngt":"≯","ngtr":"≯","nGtv":"≫̸","nharr":"↮","nhArr":"⇎","nhpar":"⫲","ni":"∋","nis":"⋼","nisd":"⋺","niv":"∋","NJcy":"Њ","njcy":"њ","nlarr":"↚","nlArr":"⇍","nldr":"‥","nlE":"≦̸","nle":"≰","nleftarrow":"↚","nLeftarrow":"⇍","nleftrightarrow":"↮","nLeftrightarrow":"⇎","nleq":"≰","nleqq":"≦̸","nleqslant":"⩽̸","nles":"⩽̸","nless":"≮","nLl":"⋘̸","nlsim":"≴","nLt":"≪⃒","nlt":"≮","nltri":"⋪","nltrie":"⋬","nLtv":"≪̸","nmid":"∤","NoBreak":"⁠","NonBreakingSpace":" ","nopf":"𝕟","Nopf":"ℕ","Not":"⫬","not":"¬","NotCongruent":"≢","NotCupCap":"≭","NotDoubleVerticalBar":"∦","NotElement":"∉","NotEqual":"≠","NotEqualTilde":"≂̸","NotExists":"∄","NotGreater":"≯","NotGreaterEqual":"≱","NotGreaterFullEqual":"≧̸","NotGreaterGreater":"≫̸","NotGreaterLess":"≹","NotGreaterSlantEqual":"⩾̸","NotGreaterTilde":"≵","NotHumpDownHump":"≎̸","NotHumpEqual":"≏̸","notin":"∉","notindot":"⋵̸","notinE":"⋹̸","notinva":"∉","notinvb":"⋷","notinvc":"⋶","NotLeftTriangleBar":"⧏̸","NotLeftTriangle":"⋪","NotLeftTriangleEqual":"⋬","NotLess":"≮","NotLessEqual":"≰","NotLessGreater":"≸","NotLessLess":"≪̸","NotLessSlantEqual":"⩽̸","NotLessTilde":"≴","NotNestedGreaterGreater":"⪢̸","NotNestedLessLess":"⪡̸","notni":"∌","notniva":"∌","notnivb":"⋾","notnivc":"⋽","NotPrecedes":"⊀","NotPrecedesEqual":"⪯̸","NotPrecedesSlantEqual":"⋠","NotReverseElement":"∌","NotRightTriangleBar":"⧐̸","NotRightTriangle":"⋫","NotRightTriangleEqual":"⋭","NotSquareSubset":"⊏̸","NotSquareSubsetEqual":"⋢","NotSquareSuperset":"⊐̸","NotSquareSupersetEqual":"⋣","NotSubset":"⊂⃒","NotSubsetEqual":"⊈","NotSucceeds":"⊁","NotSucceedsEqual":"⪰̸","NotSucceedsSlantEqual":"⋡","NotSucceedsTilde":"≿̸","NotSuperset":"⊃⃒","NotSupersetEqual":"⊉","NotTilde":"≁","NotTildeEqual":"≄","NotTildeFullEqual":"≇","NotTildeTilde":"≉","NotVerticalBar":"∤","nparallel":"∦","npar":"∦","nparsl":"⫽⃥","npart":"∂̸","npolint":"⨔","npr":"⊀","nprcue":"⋠","nprec":"⊀","npreceq":"⪯̸","npre":"⪯̸","nrarrc":"⤳̸","nrarr":"↛","nrArr":"⇏","nrarrw":"↝̸","nrightarrow":"↛","nRightarrow":"⇏","nrtri":"⋫","nrtrie":"⋭","nsc":"⊁","nsccue":"⋡","nsce":"⪰̸","Nscr":"𝒩","nscr":"𝓃","nshortmid":"∤","nshortparallel":"∦","nsim":"≁","nsime":"≄","nsimeq":"≄","nsmid":"∤","nspar":"∦","nsqsube":"⋢","nsqsupe":"⋣","nsub":"⊄","nsubE":"⫅̸","nsube":"⊈","nsubset":"⊂⃒","nsubseteq":"⊈","nsubseteqq":"⫅̸","nsucc":"⊁","nsucceq":"⪰̸","nsup":"⊅","nsupE":"⫆̸","nsupe":"⊉","nsupset":"⊃⃒","nsupseteq":"⊉","nsupseteqq":"⫆̸","ntgl":"≹","Ntilde":"Ñ","ntilde":"ñ","ntlg":"≸","ntriangleleft":"⋪","ntrianglelefteq":"⋬","ntriangleright":"⋫","ntrianglerighteq":"⋭","Nu":"Ν","nu":"ν","num":"#","numero":"№","numsp":" ","nvap":"≍⃒","nvdash":"⊬","nvDash":"⊭","nVdash":"⊮","nVDash":"⊯","nvge":"≥⃒","nvgt":">⃒","nvHarr":"⤄","nvinfin":"⧞","nvlArr":"⤂","nvle":"≤⃒","nvlt":"<⃒","nvltrie":"⊴⃒","nvrArr":"⤃","nvrtrie":"⊵⃒","nvsim":"∼⃒","nwarhk":"⤣","nwarr":"↖","nwArr":"⇖","nwarrow":"↖","nwnear":"⤧","Oacute":"Ó","oacute":"ó","oast":"⊛","Ocirc":"Ô","ocirc":"ô","ocir":"⊚","Ocy":"О","ocy":"о","odash":"⊝","Odblac":"Ő","odblac":"ő","odiv":"⨸","odot":"⊙","odsold":"⦼","OElig":"Œ","oelig":"œ","ofcir":"⦿","Ofr":"𝔒","ofr":"𝔬","ogon":"˛","Ograve":"Ò","ograve":"ò","ogt":"⧁","ohbar":"⦵","ohm":"Ω","oint":"∮","olarr":"↺","olcir":"⦾","olcross":"⦻","oline":"‾","olt":"⧀","Omacr":"Ō","omacr":"ō","Omega":"Ω","omega":"ω","Omicron":"Ο","omicron":"ο","omid":"⦶","ominus":"⊖","Oopf":"𝕆","oopf":"𝕠","opar":"⦷","OpenCurlyDoubleQuote":"“","OpenCurlyQuote":"‘","operp":"⦹","oplus":"⊕","orarr":"↻","Or":"⩔","or":"∨","ord":"⩝","order":"ℴ","orderof":"ℴ","ordf":"ª","ordm":"º","origof":"⊶","oror":"⩖","orslope":"⩗","orv":"⩛","oS":"Ⓢ","Oscr":"𝒪","oscr":"ℴ","Oslash":"Ø","oslash":"ø","osol":"⊘","Otilde":"Õ","otilde":"õ","otimesas":"⨶","Otimes":"⨷","otimes":"⊗","Ouml":"Ö","ouml":"ö","ovbar":"⌽","OverBar":"‾","OverBrace":"⏞","OverBracket":"⎴","OverParenthesis":"⏜","para":"¶","parallel":"∥","par":"∥","parsim":"⫳","parsl":"⫽","part":"∂","PartialD":"∂","Pcy":"П","pcy":"п","percnt":"%","period":".","permil":"‰","perp":"⊥","pertenk":"‱","Pfr":"𝔓","pfr":"𝔭","Phi":"Φ","phi":"φ","phiv":"ϕ","phmmat":"ℳ","phone":"☎","Pi":"Π","pi":"π","pitchfork":"⋔","piv":"ϖ","planck":"ℏ","planckh":"ℎ","plankv":"ℏ","plusacir":"⨣","plusb":"⊞","pluscir":"⨢","plus":"+","plusdo":"∔","plusdu":"⨥","pluse":"⩲","PlusMinus":"±","plusmn":"±","plussim":"⨦","plustwo":"⨧","pm":"±","Poincareplane":"ℌ","pointint":"⨕","popf":"𝕡","Popf":"ℙ","pound":"£","prap":"⪷","Pr":"⪻","pr":"≺","prcue":"≼","precapprox":"⪷","prec":"≺","preccurlyeq":"≼","Precedes":"≺","PrecedesEqual":"⪯","PrecedesSlantEqual":"≼","PrecedesTilde":"≾","preceq":"⪯","precnapprox":"⪹","precneqq":"⪵","precnsim":"⋨","pre":"⪯","prE":"⪳","precsim":"≾","prime":"′","Prime":"″","primes":"ℙ","prnap":"⪹","prnE":"⪵","prnsim":"⋨","prod":"∏","Product":"∏","profalar":"⌮","profline":"⌒","profsurf":"⌓","prop":"∝","Proportional":"∝","Proportion":"∷","propto":"∝","prsim":"≾","prurel":"⊰","Pscr":"𝒫","pscr":"𝓅","Psi":"Ψ","psi":"ψ","puncsp":" ","Qfr":"𝔔","qfr":"𝔮","qint":"⨌","qopf":"𝕢","Qopf":"ℚ","qprime":"⁗","Qscr":"𝒬","qscr":"𝓆","quaternions":"ℍ","quatint":"⨖","quest":"?","questeq":"≟","quot":"\\"","QUOT":"\\"","rAarr":"⇛","race":"∽̱","Racute":"Ŕ","racute":"ŕ","radic":"√","raemptyv":"⦳","rang":"⟩","Rang":"⟫","rangd":"⦒","range":"⦥","rangle":"⟩","raquo":"»","rarrap":"⥵","rarrb":"⇥","rarrbfs":"⤠","rarrc":"⤳","rarr":"→","Rarr":"↠","rArr":"⇒","rarrfs":"⤞","rarrhk":"↪","rarrlp":"↬","rarrpl":"⥅","rarrsim":"⥴","Rarrtl":"⤖","rarrtl":"↣","rarrw":"↝","ratail":"⤚","rAtail":"⤜","ratio":"∶","rationals":"ℚ","rbarr":"⤍","rBarr":"⤏","RBarr":"⤐","rbbrk":"❳","rbrace":"}","rbrack":"]","rbrke":"⦌","rbrksld":"⦎","rbrkslu":"⦐","Rcaron":"Ř","rcaron":"ř","Rcedil":"Ŗ","rcedil":"ŗ","rceil":"⌉","rcub":"}","Rcy":"Р","rcy":"р","rdca":"⤷","rdldhar":"⥩","rdquo":"”","rdquor":"”","rdsh":"↳","real":"ℜ","realine":"ℛ","realpart":"ℜ","reals":"ℝ","Re":"ℜ","rect":"▭","reg":"®","REG":"®","ReverseElement":"∋","ReverseEquilibrium":"⇋","ReverseUpEquilibrium":"⥯","rfisht":"⥽","rfloor":"⌋","rfr":"𝔯","Rfr":"ℜ","rHar":"⥤","rhard":"⇁","rharu":"⇀","rharul":"⥬","Rho":"Ρ","rho":"ρ","rhov":"ϱ","RightAngleBracket":"⟩","RightArrowBar":"⇥","rightarrow":"→","RightArrow":"→","Rightarrow":"⇒","RightArrowLeftArrow":"⇄","rightarrowtail":"↣","RightCeiling":"⌉","RightDoubleBracket":"⟧","RightDownTeeVector":"⥝","RightDownVectorBar":"⥕","RightDownVector":"⇂","RightFloor":"⌋","rightharpoondown":"⇁","rightharpoonup":"⇀","rightleftarrows":"⇄","rightleftharpoons":"⇌","rightrightarrows":"⇉","rightsquigarrow":"↝","RightTeeArrow":"↦","RightTee":"⊢","RightTeeVector":"⥛","rightthreetimes":"⋌","RightTriangleBar":"⧐","RightTriangle":"⊳","RightTriangleEqual":"⊵","RightUpDownVector":"⥏","RightUpTeeVector":"⥜","RightUpVectorBar":"⥔","RightUpVector":"↾","RightVectorBar":"⥓","RightVector":"⇀","ring":"˚","risingdotseq":"≓","rlarr":"⇄","rlhar":"⇌","rlm":"‏","rmoustache":"⎱","rmoust":"⎱","rnmid":"⫮","roang":"⟭","roarr":"⇾","robrk":"⟧","ropar":"⦆","ropf":"𝕣","Ropf":"ℝ","roplus":"⨮","rotimes":"⨵","RoundImplies":"⥰","rpar":")","rpargt":"⦔","rppolint":"⨒","rrarr":"⇉","Rrightarrow":"⇛","rsaquo":"›","rscr":"𝓇","Rscr":"ℛ","rsh":"↱","Rsh":"↱","rsqb":"]","rsquo":"’","rsquor":"’","rthree":"⋌","rtimes":"⋊","rtri":"▹","rtrie":"⊵","rtrif":"▸","rtriltri":"⧎","RuleDelayed":"⧴","ruluhar":"⥨","rx":"℞","Sacute":"Ś","sacute":"ś","sbquo":"‚","scap":"⪸","Scaron":"Š","scaron":"š","Sc":"⪼","sc":"≻","sccue":"≽","sce":"⪰","scE":"⪴","Scedil":"Ş","scedil":"ş","Scirc":"Ŝ","scirc":"ŝ","scnap":"⪺","scnE":"⪶","scnsim":"⋩","scpolint":"⨓","scsim":"≿","Scy":"С","scy":"с","sdotb":"⊡","sdot":"⋅","sdote":"⩦","searhk":"⤥","searr":"↘","seArr":"⇘","searrow":"↘","sect":"§","semi":";","seswar":"⤩","setminus":"∖","setmn":"∖","sext":"✶","Sfr":"𝔖","sfr":"𝔰","sfrown":"⌢","sharp":"♯","SHCHcy":"Щ","shchcy":"щ","SHcy":"Ш","shcy":"ш","ShortDownArrow":"↓","ShortLeftArrow":"←","shortmid":"∣","shortparallel":"∥","ShortRightArrow":"→","ShortUpArrow":"↑","shy":"­","Sigma":"Σ","sigma":"σ","sigmaf":"ς","sigmav":"ς","sim":"∼","simdot":"⩪","sime":"≃","simeq":"≃","simg":"⪞","simgE":"⪠","siml":"⪝","simlE":"⪟","simne":"≆","simplus":"⨤","simrarr":"⥲","slarr":"←","SmallCircle":"∘","smallsetminus":"∖","smashp":"⨳","smeparsl":"⧤","smid":"∣","smile":"⌣","smt":"⪪","smte":"⪬","smtes":"⪬︀","SOFTcy":"Ь","softcy":"ь","solbar":"⌿","solb":"⧄","sol":"/","Sopf":"𝕊","sopf":"𝕤","spades":"♠","spadesuit":"♠","spar":"∥","sqcap":"⊓","sqcaps":"⊓︀","sqcup":"⊔","sqcups":"⊔︀","Sqrt":"√","sqsub":"⊏","sqsube":"⊑","sqsubset":"⊏","sqsubseteq":"⊑","sqsup":"⊐","sqsupe":"⊒","sqsupset":"⊐","sqsupseteq":"⊒","square":"□","Square":"□","SquareIntersection":"⊓","SquareSubset":"⊏","SquareSubsetEqual":"⊑","SquareSuperset":"⊐","SquareSupersetEqual":"⊒","SquareUnion":"⊔","squarf":"▪","squ":"□","squf":"▪","srarr":"→","Sscr":"𝒮","sscr":"𝓈","ssetmn":"∖","ssmile":"⌣","sstarf":"⋆","Star":"⋆","star":"☆","starf":"★","straightepsilon":"ϵ","straightphi":"ϕ","strns":"¯","sub":"⊂","Sub":"⋐","subdot":"⪽","subE":"⫅","sube":"⊆","subedot":"⫃","submult":"⫁","subnE":"⫋","subne":"⊊","subplus":"⪿","subrarr":"⥹","subset":"⊂","Subset":"⋐","subseteq":"⊆","subseteqq":"⫅","SubsetEqual":"⊆","subsetneq":"⊊","subsetneqq":"⫋","subsim":"⫇","subsub":"⫕","subsup":"⫓","succapprox":"⪸","succ":"≻","succcurlyeq":"≽","Succeeds":"≻","SucceedsEqual":"⪰","SucceedsSlantEqual":"≽","SucceedsTilde":"≿","succeq":"⪰","succnapprox":"⪺","succneqq":"⪶","succnsim":"⋩","succsim":"≿","SuchThat":"∋","sum":"∑","Sum":"∑","sung":"♪","sup1":"¹","sup2":"²","sup3":"³","sup":"⊃","Sup":"⋑","supdot":"⪾","supdsub":"⫘","supE":"⫆","supe":"⊇","supedot":"⫄","Superset":"⊃","SupersetEqual":"⊇","suphsol":"⟉","suphsub":"⫗","suplarr":"⥻","supmult":"⫂","supnE":"⫌","supne":"⊋","supplus":"⫀","supset":"⊃","Supset":"⋑","supseteq":"⊇","supseteqq":"⫆","supsetneq":"⊋","supsetneqq":"⫌","supsim":"⫈","supsub":"⫔","supsup":"⫖","swarhk":"⤦","swarr":"↙","swArr":"⇙","swarrow":"↙","swnwar":"⤪","szlig":"ß","Tab":"\\t","target":"⌖","Tau":"Τ","tau":"τ","tbrk":"⎴","Tcaron":"Ť","tcaron":"ť","Tcedil":"Ţ","tcedil":"ţ","Tcy":"Т","tcy":"т","tdot":"⃛","telrec":"⌕","Tfr":"𝔗","tfr":"𝔱","there4":"∴","therefore":"∴","Therefore":"∴","Theta":"Θ","theta":"θ","thetasym":"ϑ","thetav":"ϑ","thickapprox":"≈","thicksim":"∼","ThickSpace":"  ","ThinSpace":" ","thinsp":" ","thkap":"≈","thksim":"∼","THORN":"Þ","thorn":"þ","tilde":"˜","Tilde":"∼","TildeEqual":"≃","TildeFullEqual":"≅","TildeTilde":"≈","timesbar":"⨱","timesb":"⊠","times":"×","timesd":"⨰","tint":"∭","toea":"⤨","topbot":"⌶","topcir":"⫱","top":"⊤","Topf":"𝕋","topf":"𝕥","topfork":"⫚","tosa":"⤩","tprime":"‴","trade":"™","TRADE":"™","triangle":"▵","triangledown":"▿","triangleleft":"◃","trianglelefteq":"⊴","triangleq":"≜","triangleright":"▹","trianglerighteq":"⊵","tridot":"◬","trie":"≜","triminus":"⨺","TripleDot":"⃛","triplus":"⨹","trisb":"⧍","tritime":"⨻","trpezium":"⏢","Tscr":"𝒯","tscr":"𝓉","TScy":"Ц","tscy":"ц","TSHcy":"Ћ","tshcy":"ћ","Tstrok":"Ŧ","tstrok":"ŧ","twixt":"≬","twoheadleftarrow":"↞","twoheadrightarrow":"↠","Uacute":"Ú","uacute":"ú","uarr":"↑","Uarr":"↟","uArr":"⇑","Uarrocir":"⥉","Ubrcy":"Ў","ubrcy":"ў","Ubreve":"Ŭ","ubreve":"ŭ","Ucirc":"Û","ucirc":"û","Ucy":"У","ucy":"у","udarr":"⇅","Udblac":"Ű","udblac":"ű","udhar":"⥮","ufisht":"⥾","Ufr":"𝔘","ufr":"𝔲","Ugrave":"Ù","ugrave":"ù","uHar":"⥣","uharl":"↿","uharr":"↾","uhblk":"▀","ulcorn":"⌜","ulcorner":"⌜","ulcrop":"⌏","ultri":"◸","Umacr":"Ū","umacr":"ū","uml":"¨","UnderBar":"_","UnderBrace":"⏟","UnderBracket":"⎵","UnderParenthesis":"⏝","Union":"⋃","UnionPlus":"⊎","Uogon":"Ų","uogon":"ų","Uopf":"𝕌","uopf":"𝕦","UpArrowBar":"⤒","uparrow":"↑","UpArrow":"↑","Uparrow":"⇑","UpArrowDownArrow":"⇅","updownarrow":"↕","UpDownArrow":"↕","Updownarrow":"⇕","UpEquilibrium":"⥮","upharpoonleft":"↿","upharpoonright":"↾","uplus":"⊎","UpperLeftArrow":"↖","UpperRightArrow":"↗","upsi":"υ","Upsi":"ϒ","upsih":"ϒ","Upsilon":"Υ","upsilon":"υ","UpTeeArrow":"↥","UpTee":"⊥","upuparrows":"⇈","urcorn":"⌝","urcorner":"⌝","urcrop":"⌎","Uring":"Ů","uring":"ů","urtri":"◹","Uscr":"𝒰","uscr":"𝓊","utdot":"⋰","Utilde":"Ũ","utilde":"ũ","utri":"▵","utrif":"▴","uuarr":"⇈","Uuml":"Ü","uuml":"ü","uwangle":"⦧","vangrt":"⦜","varepsilon":"ϵ","varkappa":"ϰ","varnothing":"∅","varphi":"ϕ","varpi":"ϖ","varpropto":"∝","varr":"↕","vArr":"⇕","varrho":"ϱ","varsigma":"ς","varsubsetneq":"⊊︀","varsubsetneqq":"⫋︀","varsupsetneq":"⊋︀","varsupsetneqq":"⫌︀","vartheta":"ϑ","vartriangleleft":"⊲","vartriangleright":"⊳","vBar":"⫨","Vbar":"⫫","vBarv":"⫩","Vcy":"В","vcy":"в","vdash":"⊢","vDash":"⊨","Vdash":"⊩","VDash":"⊫","Vdashl":"⫦","veebar":"⊻","vee":"∨","Vee":"⋁","veeeq":"≚","vellip":"⋮","verbar":"|","Verbar":"‖","vert":"|","Vert":"‖","VerticalBar":"∣","VerticalLine":"|","VerticalSeparator":"❘","VerticalTilde":"≀","VeryThinSpace":" ","Vfr":"𝔙","vfr":"𝔳","vltri":"⊲","vnsub":"⊂⃒","vnsup":"⊃⃒","Vopf":"𝕍","vopf":"𝕧","vprop":"∝","vrtri":"⊳","Vscr":"𝒱","vscr":"𝓋","vsubnE":"⫋︀","vsubne":"⊊︀","vsupnE":"⫌︀","vsupne":"⊋︀","Vvdash":"⊪","vzigzag":"⦚","Wcirc":"Ŵ","wcirc":"ŵ","wedbar":"⩟","wedge":"∧","Wedge":"⋀","wedgeq":"≙","weierp":"℘","Wfr":"𝔚","wfr":"𝔴","Wopf":"𝕎","wopf":"𝕨","wp":"℘","wr":"≀","wreath":"≀","Wscr":"𝒲","wscr":"𝓌","xcap":"⋂","xcirc":"◯","xcup":"⋃","xdtri":"▽","Xfr":"𝔛","xfr":"𝔵","xharr":"⟷","xhArr":"⟺","Xi":"Ξ","xi":"ξ","xlarr":"⟵","xlArr":"⟸","xmap":"⟼","xnis":"⋻","xodot":"⨀","Xopf":"𝕏","xopf":"𝕩","xoplus":"⨁","xotime":"⨂","xrarr":"⟶","xrArr":"⟹","Xscr":"𝒳","xscr":"𝓍","xsqcup":"⨆","xuplus":"⨄","xutri":"△","xvee":"⋁","xwedge":"⋀","Yacute":"Ý","yacute":"ý","YAcy":"Я","yacy":"я","Ycirc":"Ŷ","ycirc":"ŷ","Ycy":"Ы","ycy":"ы","yen":"¥","Yfr":"𝔜","yfr":"𝔶","YIcy":"Ї","yicy":"ї","Yopf":"𝕐","yopf":"𝕪","Yscr":"𝒴","yscr":"𝓎","YUcy":"Ю","yucy":"ю","yuml":"ÿ","Yuml":"Ÿ","Zacute":"Ź","zacute":"ź","Zcaron":"Ž","zcaron":"ž","Zcy":"З","zcy":"з","Zdot":"Ż","zdot":"ż","zeetrf":"ℨ","ZeroWidthSpace":"​","Zeta":"Ζ","zeta":"ζ","zfr":"𝔷","Zfr":"ℨ","ZHcy":"Ж","zhcy":"ж","zigrarr":"⇝","zopf":"𝕫","Zopf":"ℤ","Zscr":"𝒵","zscr":"𝓏","zwj":"‍","zwnj":"‌"}')},function(e,t,r){"use strict";var n={};function s(e,t,r){var o,i,a,u,c,l="";for("string"!=typeof t&&(r=t,t=s.defaultChars),void 0===r&&(r=!0),c=function(e){var t,r,s=n[e];if(s)return s;for(s=n[e]=[],t=0;t<128;t++)r=String.fromCharCode(t),/^[0-9a-z]$/i.test(r)?s.push(r):s.push("%"+("0"+t.toString(16).toUpperCase()).slice(-2));for(t=0;t=55296&&a<=57343){if(a>=55296&&a<=56319&&o+1=56320&&u<=57343){l+=encodeURIComponent(e[o]+e[o+1]),o++;continue}l+="%EF%BF%BD"}else l+=encodeURIComponent(e[o]);return l}s.defaultChars=";/?:@&=+$,-_.!~*'()#",s.componentChars="-_.!~*'()",e.exports=s},function(e,t,r){"use strict";var n={};function s(e,t){var r;return"string"!=typeof t&&(t=s.defaultChars),r=function(e){var t,r,s=n[e];if(s)return s;for(s=n[e]=[],t=0;t<128;t++)r=String.fromCharCode(t),s.push(r);for(t=0;t=55296&&u<=57343?"���":String.fromCharCode(u),t+=6):240==(248&s)&&t+91114111?c+="����":(u-=65536,c+=String.fromCharCode(55296+(u>>10),56320+(1023&u))),t+=9):c+="�";return c}))}s.defaultChars=";/?:@&=+$,#",s.componentChars="",e.exports=s},function(e,t,r){"use strict";e.exports=function(e){var t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",e.hostname&&-1!==e.hostname.indexOf(":")?t+="["+e.hostname+"]":t+=e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",t+=e.search||"",t+=e.hash||""}},function(e,t,r){"use strict";function n(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}var s=/^([a-z0-9.+-]+:)/i,o=/:[0-9]*$/,i=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,a=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),u=["'"].concat(a),c=["%","/","?",";","#"].concat(u),l=["/","?","#"],p=/^[+a-z0-9A-Z_-]{0,63}$/,h=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,f={javascript:!0,"javascript:":!0},d={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};n.prototype.parse=function(e,t){var r,n,o,a,u,m=e;if(m=m.trim(),!t&&1===e.split("#").length){var g=i.exec(m);if(g)return this.pathname=g[1],g[2]&&(this.search=g[2]),this}var _=s.exec(m);if(_&&(o=(_=_[0]).toLowerCase(),this.protocol=_,m=m.substr(_.length)),(t||_||m.match(/^\/\/[^@\/]+@[^@\/]+/))&&(!(u="//"===m.substr(0,2))||_&&f[_]||(m=m.substr(2),this.slashes=!0)),!f[_]&&(u||_&&!d[_])){var b,k,C=-1;for(r=0;r127?x+="x":x+=y[F];if(!x.match(p)){var E=A.slice(0,r),q=A.slice(r+1),S=y.match(h);S&&(E.push(S[1]),q.unshift(S[2])),q.length&&(m=q.join(".")+m),this.hostname=E.join(".");break}}}}this.hostname.length>255&&(this.hostname=""),D&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}var L=m.indexOf("#");-1!==L&&(this.hash=m.substr(L),m=m.slice(0,L));var z=m.indexOf("?");return-1!==z&&(this.search=m.substr(z),m=m.slice(0,z)),m&&(this.pathname=m),d[o]&&this.hostname&&!this.pathname&&(this.pathname=""),this},n.prototype.parseHost=function(e){var t=o.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)},e.exports=function(e,t){if(e&&e instanceof n)return e;var r=new n;return r.parse(e,t),r}},function(e,t,r){"use strict";t.Any=r(18),t.Cc=r(19),t.Cf=r(20),t.P=r(4),t.Z=r(21)},function(e,t){e.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/},function(e,t){e.exports=/[\0-\x1F\x7F-\x9F]/},function(e,t){e.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804\uDCBD|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/},function(e,t){e.exports=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/},function(e,t,r){"use strict";t.parseLinkLabel=r(23),t.parseLinkDestination=r(24),t.parseLinkTitle=r(25)},function(e,t,r){"use strict";e.exports=function(e,t,r){var n,s,o,i,a=-1,u=e.posMax,c=e.pos;for(e.pos=t+1,n=1;e.pos32)return a;if(41===s){if(0===o)break;o--}t++}return i===t||0!==o||(a.str=n(e.slice(i,t)),a.lines=0,a.pos=t,a.ok=!0),a}},function(e,t,r){"use strict";var n=r(0).unescapeAll;e.exports=function(e,t,r){var s,o,i=0,a=t,u={ok:!1,pos:0,lines:0,str:""};if(t>=r)return u;if(34!==(o=e.charCodeAt(t))&&39!==o&&40!==o)return u;for(t++,40===o&&(o=41);t"+o(e[t].content)+""},i.code_block=function(e,t,r,n,s){var i=e[t];return""+o(e[t].content)+"\n"},i.fence=function(e,t,r,n,i){var a,u,c,l,p,h=e[t],f=h.info?s(h.info).trim():"",d="",m="";return f&&(d=(c=f.split(/(\s+)/g))[0],m=c.slice(2).join("")),0===(a=r.highlight&&r.highlight(h.content,d,m)||o(h.content)).indexOf(""+a+"\n"):"
"+a+"
\n"},i.image=function(e,t,r,n,s){var o=e[t];return o.attrs[o.attrIndex("alt")][1]=s.renderInlineAsText(o.children,r,n),s.renderToken(e,t,r)},i.hardbreak=function(e,t,r){return r.xhtmlOut?"
\n":"
\n"},i.softbreak=function(e,t,r){return r.breaks?r.xhtmlOut?"
\n":"
\n":"\n"},i.text=function(e,t){return o(e[t].content)},i.html_block=function(e,t){return e[t].content},i.html_inline=function(e,t){return e[t].content},a.prototype.renderAttrs=function(e){var t,r,n;if(!e.attrs)return"";for(n="",t=0,r=e.attrs.length;t\n":">")},a.prototype.renderInline=function(e,t,r){for(var n,s="",o=this.rules,i=0,a=e.length;i/i.test(e)}e.exports=function(e){var t,r,o,i,a,u,c,l,p,h,f,d,m,g,_,b,k,C,v=e.tokens;if(e.md.options.linkify)for(r=0,o=v.length;r=0;t--)if("link_close"!==(u=i[t]).type){if("html_inline"===u.type&&(C=u.content,/^\s]/i.test(C)&&m>0&&m--,s(u.content)&&m++),!(m>0)&&"text"===u.type&&e.md.linkify.test(u.content)){for(p=u.content,k=e.md.linkify.match(p),c=[],d=u.level,f=0,l=0;lf&&((a=new e.Token("text","",0)).content=p.slice(f,h),a.level=d,c.push(a)),(a=new e.Token("link_open","a",1)).attrs=[["href",_]],a.level=d++,a.markup="linkify",a.info="auto",c.push(a),(a=new e.Token("text","",0)).content=b,a.level=d,c.push(a),(a=new e.Token("link_close","a",-1)).level=--d,a.markup="linkify",a.info="auto",c.push(a),f=k[l].lastIndex);f=0;t--)"text"!==(r=e[t]).type||n||(r.content=r.content.replace(o,a)),"link_open"===r.type&&"auto"===r.info&&n--,"link_close"===r.type&&"auto"===r.info&&n++}function c(e){var t,r,s=0;for(t=e.length-1;t>=0;t--)"text"!==(r=e[t]).type||s||n.test(r.content)&&(r.content=r.content.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---(?=[^-]|$)/gm,"$1—").replace(/(^|\s)--(?=\s|$)/gm,"$1–").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm,"$1–")),"link_open"===r.type&&"auto"===r.info&&s--,"link_close"===r.type&&"auto"===r.info&&s++}e.exports=function(e){var t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&(s.test(e.tokens[t].content)&&u(e.tokens[t].children),n.test(e.tokens[t].content)&&c(e.tokens[t].children))}},function(e,t,r){"use strict";var n=r(0).isWhiteSpace,s=r(0).isPunctChar,o=r(0).isMdAsciiPunct,i=/['"]/,a=/['"]/g;function u(e,t,r){return e.substr(0,t)+r+e.substr(t+1)}function c(e,t){var r,i,c,l,p,h,f,d,m,g,_,b,k,C,v,D,A,y,x,F,w;for(x=[],r=0;r=0&&!(x[A].level<=f);A--);if(x.length=A+1,"text"===i.type){p=0,h=(c=i.content).length;e:for(;p=0)m=c.charCodeAt(l.index-1);else for(A=r-1;A>=0&&("softbreak"!==e[A].type&&"hardbreak"!==e[A].type);A--)if(e[A].content){m=e[A].content.charCodeAt(e[A].content.length-1);break}if(g=32,p=48&&m<=57&&(D=v=!1),v&&D&&(v=_,D=b),v||D){if(D)for(A=x.length-1;A>=0&&(d=x[A],!(x[A].level=0;t--)"inline"===e.tokens[t].type&&i.test(e.tokens[t].content)&&c(e.tokens[t].children,e)}},function(e,t,r){"use strict";var n=r(2);function s(e,t,r){this.src=e,this.env=r,this.tokens=[],this.inlineMode=!1,this.md=t}s.prototype.Token=n,e.exports=s},function(e,t,r){"use strict";var n=r(1),s=[["table",r(36),["paragraph","reference"]],["code",r(37)],["fence",r(38),["paragraph","reference","blockquote","list"]],["blockquote",r(39),["paragraph","reference","blockquote","list"]],["hr",r(40),["paragraph","reference","blockquote","list"]],["list",r(41),["paragraph","reference","blockquote"]],["reference",r(42)],["heading",r(43),["paragraph","reference","blockquote"]],["lheading",r(44)],["html_block",r(45),["paragraph","reference","blockquote"]],["paragraph",r(47)]];function o(){this.ruler=new n;for(var e=0;e=r))&&!(e.sCount[i]=u){e.line=r;break}for(n=0;nr)return!1;if(h=t+1,e.sCount[h]=4)return!1;if((c=e.bMarks[h]+e.tShift[h])>=e.eMarks[h])return!1;if(124!==(a=e.src.charCodeAt(c++))&&45!==a&&58!==a)return!1;for(;c=4)return!1;if((f=o(u)).length&&""===f[0]&&f.shift(),f.length&&""===f[f.length-1]&&f.pop(),0===(d=f.length)||d!==g.length)return!1;if(i)return!0;for(C=e.parentType,e.parentType="table",D=e.md.block.ruler.getRules("blockquote"),(m=e.push("table_open","table",1)).map=b=[t,0],(m=e.push("thead_open","thead",1)).map=[t,t+1],(m=e.push("tr_open","tr",1)).map=[t,t+1],l=0;l=4)break;for((f=o(u)).length&&""===f[0]&&f.shift(),f.length&&""===f[f.length-1]&&f.pop(),h===t+2&&((m=e.push("tbody_open","tbody",1)).map=k=[t+2,0]),(m=e.push("tr_open","tr",1)).map=[h,h+1],l=0;l=4))break;s=++n}return e.line=s,(o=e.push("code_block","code",0)).content=e.getLines(t,s,4+e.blkIndent,!0),o.map=[t,e.line],!0}},function(e,t,r){"use strict";e.exports=function(e,t,r,n){var s,o,i,a,u,c,l,p=!1,h=e.bMarks[t]+e.tShift[t],f=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(h+3>f)return!1;if(126!==(s=e.src.charCodeAt(h))&&96!==s)return!1;if(u=h,(o=(h=e.skipChars(h,s))-u)<3)return!1;if(l=e.src.slice(u,h),i=e.src.slice(h,f),96===s&&i.indexOf(String.fromCharCode(s))>=0)return!1;if(n)return!0;for(a=t;!(++a>=r)&&!((h=u=e.bMarks[a]+e.tShift[a])<(f=e.eMarks[a])&&e.sCount[a]=4||(h=e.skipChars(h,s))-u=4)return!1;if(62!==e.src.charCodeAt(F++))return!1;if(s)return!0;for(u=f=e.sCount[t]+1,32===e.src.charCodeAt(F)?(F++,u++,f++,o=!1,C=!0):9===e.src.charCodeAt(F)?(C=!0,(e.bsCount[t]+f)%4==3?(F++,u++,f++,o=!1):o=!0):C=!1,d=[e.bMarks[t]],e.bMarks[t]=F;F=w,b=[e.sCount[t]],e.sCount[t]=f-u,k=[e.tShift[t]],e.tShift[t]=F-e.bMarks[t],D=e.md.block.ruler.getRules("blockquote"),_=e.parentType,e.parentType="blockquote",h=t+1;h=(w=e.eMarks[h])));h++)if(62!==e.src.charCodeAt(F++)||y){if(l)break;for(v=!1,a=0,c=D.length;a=w,m.push(e.bsCount[h]),e.bsCount[h]=e.sCount[h]+1+(C?1:0),b.push(e.sCount[h]),e.sCount[h]=f-u,k.push(e.tShift[h]),e.tShift[h]=F-e.bMarks[h]}for(g=e.blkIndent,e.blkIndent=0,(A=e.push("blockquote_open","blockquote",1)).markup=">",A.map=p=[t,0],e.md.block.tokenize(e,t,h),(A=e.push("blockquote_close","blockquote",-1)).markup=">",e.lineMax=x,e.parentType=_,p[1]=e.line,a=0;a=4)return!1;if(42!==(o=e.src.charCodeAt(c++))&&45!==o&&95!==o)return!1;for(i=1;c=i)return-1;if((r=e.src.charCodeAt(o++))<48||r>57)return-1;for(;;){if(o>=i)return-1;if(!((r=e.src.charCodeAt(o++))>=48&&r<=57)){if(41===r||46===r)break;return-1}if(o-s>=10)return-1}return o=4)return!1;if(e.listIndent>=0&&e.sCount[t]-e.listIndent>=4&&e.sCount[t]=e.blkIndent&&(B=!0),(E=o(e,t))>=0){if(h=!0,S=e.bMarks[t]+e.tShift[t],b=Number(e.src.substr(S,E-S-1)),B&&1!==b)return!1}else{if(!((E=s(e,t))>=0))return!1;h=!1}if(B&&e.skipSpaces(E)>=e.eMarks[t])return!1;if(_=e.src.charCodeAt(E-1),n)return!0;for(g=e.tokens.length,h?(T=e.push("ordered_list_open","ol",1),1!==b&&(T.attrs=[["start",b]])):T=e.push("bullet_list_open","ul",1),T.map=m=[t,0],T.markup=String.fromCharCode(_),C=t,q=!1,z=e.md.block.ruler.getRules("list"),A=e.parentType,e.parentType="list";C=k?1:v-p)>4&&(l=1),c=p+l,(T=e.push("list_item_open","li",1)).markup=String.fromCharCode(_),T.map=f=[t,0],F=e.tight,x=e.tShift[t],y=e.sCount[t],D=e.listIndent,e.listIndent=e.blkIndent,e.blkIndent=c,e.tight=!0,e.tShift[t]=a-e.bMarks[t],e.sCount[t]=v,a>=k&&e.isEmpty(t+1)?e.line=Math.min(e.line+2,r):e.md.block.tokenize(e,t,r,!0),e.tight&&!q||(I=!1),q=e.line-t>1&&e.isEmpty(e.line-1),e.blkIndent=e.listIndent,e.listIndent=D,e.tShift[t]=x,e.sCount[t]=y,e.tight=F,(T=e.push("list_item_close","li",-1)).markup=String.fromCharCode(_),C=t=e.line,f[1]=C,a=e.bMarks[t],C>=r)break;if(e.sCount[C]=4)break;for(L=!1,u=0,d=z.length;u=4)return!1;if(91!==e.src.charCodeAt(A))return!1;for(;++A3||e.sCount[x]<0)){for(k=!1,p=0,h=C.length;p=4)return!1;if(35!==(o=e.src.charCodeAt(c))||c>=l)return!1;for(i=1,o=e.src.charCodeAt(++c);35===o&&c6||cc&&n(e.src.charCodeAt(a-1))&&(l=a),e.line=t+1,(u=e.push("heading_open","h"+String(i),1)).markup="########".slice(0,i),u.map=[t,e.line],(u=e.push("inline","",0)).content=e.src.slice(c,l).trim(),u.map=[t,e.line],u.children=[],(u=e.push("heading_close","h"+String(i),-1)).markup="########".slice(0,i)),!0)}},function(e,t,r){"use strict";e.exports=function(e,t,r){var n,s,o,i,a,u,c,l,p,h,f=t+1,d=e.md.block.ruler.getRules("paragraph");if(e.sCount[t]-e.blkIndent>=4)return!1;for(h=e.parentType,e.parentType="paragraph";f3)){if(e.sCount[f]>=e.blkIndent&&(u=e.bMarks[f]+e.tShift[f])<(c=e.eMarks[f])&&(45===(p=e.src.charCodeAt(u))||61===p)&&(u=e.skipChars(u,p),(u=e.skipSpaces(u))>=c)){l=61===p?1:2;break}if(!(e.sCount[f]<0)){for(s=!1,o=0,i=d.length;o|$))/i,/<\/(script|pre|style)>/i,!0],[/^/,!0],[/^<\?/,/\?>/,!0],[/^/,!0],[/^/,!0],[new RegExp("^|$))","i"),/^$/,!0],[new RegExp(s.source+"\\s*$"),/^$/,!1]];e.exports=function(e,t,r,n){var s,i,a,u,c=e.bMarks[t]+e.tShift[t],l=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(c))return!1;for(u=e.src.slice(c,l),s=0;s3||e.sCount[u]<0)){for(n=!1,s=0,o=c.length;s0&&this.level++,this.tokens.push(s),s},o.prototype.isEmpty=function(e){return this.bMarks[e]+this.tShift[e]>=this.eMarks[e]},o.prototype.skipEmptyLines=function(e){for(var t=this.lineMax;et;)if(!s(this.src.charCodeAt(--e)))return e+1;return e},o.prototype.skipChars=function(e,t){for(var r=this.src.length;er;)if(t!==this.src.charCodeAt(--e))return e+1;return e},o.prototype.getLines=function(e,t,r,n){var o,i,a,u,c,l,p,h=e;if(e>=t)return"";for(l=new Array(t-e),o=0;hr?new Array(i-r+1).join(" ")+this.src.slice(u,c):this.src.slice(u,c)}return l.join("")},o.prototype.Token=n,e.exports=o},function(e,t,r){"use strict";var n=r(1),s=[["text",r(50)],["newline",r(51)],["escape",r(52)],["backticks",r(53)],["strikethrough",r(7).tokenize],["emphasis",r(8).tokenize],["link",r(54)],["image",r(55)],["autolink",r(56)],["html_inline",r(57)],["entity",r(58)]],o=[["balance_pairs",r(59)],["strikethrough",r(7).postProcess],["emphasis",r(8).postProcess],["text_collapse",r(60)]];function i(){var e;for(this.ruler=new n,e=0;e=o)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},i.prototype.parse=function(e,t,r,n){var s,o,i,a=new this.State(e,t,r,n);for(this.tokenize(a),i=(o=this.ruler2.getRules("")).length,s=0;s=0&&32===e.pending.charCodeAt(r)?r>=1&&32===e.pending.charCodeAt(r-1)?(e.pending=e.pending.replace(/ +$/,""),e.push("hardbreak","br",0)):(e.pending=e.pending.slice(0,-1),e.push("softbreak","br",0)):e.push("softbreak","br",0)),o++;o?@[]^_`{|}~-".split("").forEach((function(e){s[e.charCodeAt(0)]=1})),e.exports=function(e,t){var r,o=e.pos,i=e.posMax;if(92!==e.src.charCodeAt(o))return!1;if(++o=m)return!1;if(g=c,(l=e.md.helpers.parseLinkDestination(e.src,c,e.posMax)).ok){for(h=e.md.normalizeLink(l.str),e.md.validateLink(h)?c=l.pos:h="",g=c;c=m||41!==e.src.charCodeAt(c))&&(_=!0),c++}if(_){if(void 0===e.env.references)return!1;if(c=0?i=e.src.slice(g,c++):c=a+1):c=a+1,i||(i=e.src.slice(u,a)),!(p=e.env.references[n(i)]))return e.pos=d,!1;h=p.href,f=p.title}return t||(e.pos=u,e.posMax=a,e.push("link_open","a",1).attrs=r=[["href",h]],f&&r.push(["title",f]),e.md.inline.tokenize(e),e.push("link_close","a",-1)),e.pos=c,e.posMax=m,!0}},function(e,t,r){"use strict";var n=r(0).normalizeReference,s=r(0).isSpace;e.exports=function(e,t){var r,o,i,a,u,c,l,p,h,f,d,m,g,_="",b=e.pos,k=e.posMax;if(33!==e.src.charCodeAt(e.pos))return!1;if(91!==e.src.charCodeAt(e.pos+1))return!1;if(c=e.pos+2,(u=e.md.helpers.parseLinkLabel(e,e.pos+1,!1))<0)return!1;if((l=u+1)=k)return!1;for(g=l,(h=e.md.helpers.parseLinkDestination(e.src,l,e.posMax)).ok&&(_=e.md.normalizeLink(h.str),e.md.validateLink(_)?l=h.pos:_=""),g=l;l=k||41!==e.src.charCodeAt(l))return e.pos=b,!1;l++}else{if(void 0===e.env.references)return!1;if(l=0?a=e.src.slice(g,l++):l=u+1):l=u+1,a||(a=e.src.slice(c,u)),!(p=e.env.references[n(a)]))return e.pos=b,!1;_=p.href,f=p.title}return t||(i=e.src.slice(c,u),e.md.inline.parse(i,e.md,e.env,m=[]),(d=e.push("image","img",0)).attrs=r=[["src",_],["alt",""]],d.children=m,d.content=i,f&&r.push(["title",f])),e.pos=l,e.posMax=k,!0}},function(e,t,r){"use strict";var n=/^([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/,s=/^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$/;e.exports=function(e,t){var r,o,i,a,u,c,l=e.pos;if(60!==e.src.charCodeAt(l))return!1;for(u=e.pos,c=e.posMax;;){if(++l>=c)return!1;if(60===(a=e.src.charCodeAt(l)))return!1;if(62===a)break}return r=e.src.slice(u+1,l),s.test(r)?(o=e.md.normalizeLink(r),!!e.md.validateLink(o)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",o]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(r),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=r.length+2,!0)):!!n.test(r)&&(o=e.md.normalizeLink("mailto:"+r),!!e.md.validateLink(o)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",o]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(r),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=r.length+2,!0))}},function(e,t,r){"use strict";var n=r(6).HTML_TAG_RE;e.exports=function(e,t){var r,s,o,i=e.pos;return!!e.md.options.html&&(o=e.posMax,!(60!==e.src.charCodeAt(i)||i+2>=o)&&(!(33!==(r=e.src.charCodeAt(i+1))&&63!==r&&47!==r&&!function(e){var t=32|e;return t>=97&&t<=122}(r))&&(!!(s=e.src.slice(i).match(n))&&(t||(e.push("html_inline","",0).content=e.src.slice(i,i+s[0].length)),e.pos+=s[0].length,!0))))}},function(e,t,r){"use strict";var n=r(3),s=r(0).has,o=r(0).isValidEntityCode,i=r(0).fromCodePoint,a=/^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i,u=/^&([a-z][a-z0-9]{1,31});/i;e.exports=function(e,t){var r,c,l=e.pos,p=e.posMax;if(38!==e.src.charCodeAt(l))return!1;if(l+1i;n-=o.jump+1)if((o=t[n]).marker===s.marker&&o.open&&o.end<0&&(u=!1,(o.close||s.open)&&(o.length+s.length)%3==0&&(o.length%3==0&&s.length%3==0||(u=!0)),!u)){c=n>0&&!t[n-1].open?t[n-1].jump+1:0,s.jump=r-n+c,s.open=!1,o.end=r,o.jump=c,o.close=!1,a=-1;break}-1!==a&&(l[s.marker][(s.length||0)%3]=a)}}e.exports=function(e){var t,r=e.tokens_meta,s=e.tokens_meta.length;for(n(0,e.delimiters),t=0;t0&&n++,"text"===s[t].type&&t+10&&(this.level++,this._prev_delimiters.push(this.delimiters),this.delimiters=[],o={delimiters:this.delimiters}),this.pendingLevel=this.level,this.tokens.push(s),this.tokens_meta.push(o),s},a.prototype.scanDelims=function(e,t){var r,n,a,u,c,l,p,h,f,d=e,m=!0,g=!0,_=this.posMax,b=this.src.charCodeAt(e);for(r=e>0?this.src.charCodeAt(e-1):32;d<_&&this.src.charCodeAt(d)===b;)d++;return a=d-e,n=d<_?this.src.charCodeAt(d):32,p=i(r)||o(String.fromCharCode(r)),f=i(n)||o(String.fromCharCode(n)),l=s(r),(h=s(n))?m=!1:f&&(l||p||(m=!1)),l?g=!1:p&&(h||f||(g=!1)),t?(u=m,c=g):(u=m&&(!g||p),c=g&&(!m||f)),{can_open:u,can_close:c,length:a}},a.prototype.Token=n,e.exports=a},function(e,t,r){"use strict";function n(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach((function(t){t&&Object.keys(t).forEach((function(r){e[r]=t[r]}))})),e}function s(e){return Object.prototype.toString.call(e)}function o(e){return"[object Function]"===s(e)}function i(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}var a={fuzzyLink:!0,fuzzyEmail:!0,fuzzyIP:!1};var u={"http:":{validate:function(e,t,r){var n=e.slice(t);return r.re.http||(r.re.http=new RegExp("^\\/\\/"+r.re.src_auth+r.re.src_host_port_strict+r.re.src_path,"i")),r.re.http.test(n)?n.match(r.re.http)[0].length:0}},"https:":"http:","ftp:":"http:","//":{validate:function(e,t,r){var n=e.slice(t);return r.re.no_http||(r.re.no_http=new RegExp("^"+r.re.src_auth+"(?:localhost|(?:(?:"+r.re.src_domain+")\\.)+"+r.re.src_domain_root+")"+r.re.src_port+r.re.src_host_terminator+r.re.src_path,"i")),r.re.no_http.test(n)?t>=3&&":"===e[t-3]||t>=3&&"/"===e[t-3]?0:n.match(r.re.no_http)[0].length:0}},"mailto:":{validate:function(e,t,r){var n=e.slice(t);return r.re.mailto||(r.re.mailto=new RegExp("^"+r.re.src_email_name+"@"+r.re.src_host_strict,"i")),r.re.mailto.test(n)?n.match(r.re.mailto)[0].length:0}}},c="biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split("|");function l(e){var t=e.re=r(63)(e.__opts__),n=e.__tlds__.slice();function a(e){return e.replace("%TLDS%",t.src_tlds)}e.onCompile(),e.__tlds_replaced__||n.push("a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]"),n.push(t.src_xn),t.src_tlds=n.join("|"),t.email_fuzzy=RegExp(a(t.tpl_email_fuzzy),"i"),t.link_fuzzy=RegExp(a(t.tpl_link_fuzzy),"i"),t.link_no_ip_fuzzy=RegExp(a(t.tpl_link_no_ip_fuzzy),"i"),t.host_fuzzy_test=RegExp(a(t.tpl_host_fuzzy_test),"i");var u=[];function c(e,t){throw new Error('(LinkifyIt) Invalid schema "'+e+'": '+t)}e.__compiled__={},Object.keys(e.__schemas__).forEach((function(t){var r=e.__schemas__[t];if(null!==r){var n={validate:null,link:null};if(e.__compiled__[t]=n,"[object Object]"===s(r))return!function(e){return"[object RegExp]"===s(e)}(r.validate)?o(r.validate)?n.validate=r.validate:c(t,r):n.validate=function(e){return function(t,r){var n=t.slice(r);return e.test(n)?n.match(e)[0].length:0}}(r.validate),void(o(r.normalize)?n.normalize=r.normalize:r.normalize?c(t,r):n.normalize=function(e,t){t.normalize(e)});!function(e){return"[object String]"===s(e)}(r)?c(t,r):u.push(t)}})),u.forEach((function(t){e.__compiled__[e.__schemas__[t]]&&(e.__compiled__[t].validate=e.__compiled__[e.__schemas__[t]].validate,e.__compiled__[t].normalize=e.__compiled__[e.__schemas__[t]].normalize)})),e.__compiled__[""]={validate:null,normalize:function(e,t){t.normalize(e)}};var l=Object.keys(e.__compiled__).filter((function(t){return t.length>0&&e.__compiled__[t]})).map(i).join("|");e.re.schema_test=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+l+")","i"),e.re.schema_search=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+l+")","ig"),e.re.pretest=RegExp("("+e.re.schema_test.source+")|("+e.re.host_fuzzy_test.source+")|@","i"),function(e){e.__index__=-1,e.__text_cache__=""}(e)}function p(e,t){var r=e.__index__,n=e.__last_index__,s=e.__text_cache__.slice(r,n);this.schema=e.__schema__.toLowerCase(),this.index=r+t,this.lastIndex=n+t,this.raw=s,this.text=s,this.url=s}function h(e,t){var r=new p(e,t);return e.__compiled__[r.schema].normalize(r,e),r}function f(e,t){if(!(this instanceof f))return new f(e,t);var r;t||(r=e,Object.keys(r||{}).reduce((function(e,t){return e||a.hasOwnProperty(t)}),!1)&&(t=e,e={})),this.__opts__=n({},a,t),this.__index__=-1,this.__last_index__=-1,this.__schema__="",this.__text_cache__="",this.__schemas__=n({},u,e),this.__compiled__={},this.__tlds__=c,this.__tlds_replaced__=!1,this.re={},l(this)}f.prototype.add=function(e,t){return this.__schemas__[e]=t,l(this),this},f.prototype.set=function(e){return this.__opts__=n(this.__opts__,e),this},f.prototype.test=function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return!1;var t,r,n,s,o,i,a,u;if(this.re.schema_test.test(e))for((a=this.re.schema_search).lastIndex=0;null!==(t=a.exec(e));)if(s=this.testSchemaAt(e,t[2],a.lastIndex)){this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+s;break}return this.__opts__.fuzzyLink&&this.__compiled__["http:"]&&(u=e.search(this.re.host_fuzzy_test))>=0&&(this.__index__<0||u=0&&null!==(n=e.match(this.re.email_fuzzy))&&(o=n.index+n[1].length,i=n.index+n[0].length,(this.__index__<0||othis.__last_index__)&&(this.__schema__="mailto:",this.__index__=o,this.__last_index__=i)),this.__index__>=0},f.prototype.pretest=function(e){return this.re.pretest.test(e)},f.prototype.testSchemaAt=function(e,t,r){return this.__compiled__[t.toLowerCase()]?this.__compiled__[t.toLowerCase()].validate(e,r,this):0},f.prototype.match=function(e){var t=0,r=[];this.__index__>=0&&this.__text_cache__===e&&(r.push(h(this,t)),t=this.__last_index__);for(var n=t?e.slice(t):e;this.test(n);)r.push(h(this,t)),n=n.slice(this.__last_index__),t+=this.__last_index__;return r.length?r:null},f.prototype.tlds=function(e,t){return e=Array.isArray(e)?e:[e],t?(this.__tlds__=this.__tlds__.concat(e).sort().filter((function(e,t,r){return e!==r[t-1]})).reverse(),l(this),this):(this.__tlds__=e.slice(),this.__tlds_replaced__=!0,l(this),this)},f.prototype.normalize=function(e){e.schema||(e.url="http://"+e.url),"mailto:"!==e.schema||/^mailto:/i.test(e.url)||(e.url="mailto:"+e.url)},f.prototype.onCompile=function(){},e.exports=f},function(e,t,r){"use strict";e.exports=function(e){var t={};t.src_Any=r(64).source,t.src_Cc=r(65).source,t.src_Z=r(66).source,t.src_P=r(67).source,t.src_ZPCc=[t.src_Z,t.src_P,t.src_Cc].join("|"),t.src_ZCc=[t.src_Z,t.src_Cc].join("|");return t.src_pseudo_letter="(?:(?![><|]|"+t.src_ZPCc+")"+t.src_Any+")",t.src_ip4="(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",t.src_auth="(?:(?:(?!"+t.src_ZCc+"|[@/\\[\\]()]).)+@)?",t.src_port="(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?",t.src_host_terminator="(?=$|[><|]|"+t.src_ZPCc+")(?!-|_|:\\d|\\.-|\\.(?!$|"+t.src_ZPCc+"))",t.src_path="(?:[/?#](?:(?!"+t.src_ZCc+"|[><|]|[()[\\]{}.,\"'?!\\-]).|\\[(?:(?!"+t.src_ZCc+"|\\]).)*\\]|\\((?:(?!"+t.src_ZCc+"|[)]).)*\\)|\\{(?:(?!"+t.src_ZCc+'|[}]).)*\\}|\\"(?:(?!'+t.src_ZCc+'|["]).)+\\"|\\\'(?:(?!'+t.src_ZCc+"|[']).)+\\'|\\'(?="+t.src_pseudo_letter+"|[-]).|\\.{2,}[a-zA-Z0-9%/&]|\\.(?!"+t.src_ZCc+"|[.]).|"+(e&&e["---"]?"\\-(?!--(?:[^-]|$))(?:-*)|":"\\-+|")+"\\,(?!"+t.src_ZCc+").|\\!+(?!"+t.src_ZCc+"|[!]).|\\?(?!"+t.src_ZCc+"|[?]).)+|\\/)?",t.src_email_name='[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*',t.src_xn="xn--[a-z0-9\\-]{1,59}",t.src_domain_root="(?:"+t.src_xn+"|"+t.src_pseudo_letter+"{1,63})",t.src_domain="(?:"+t.src_xn+"|(?:"+t.src_pseudo_letter+")|(?:"+t.src_pseudo_letter+"(?:-|"+t.src_pseudo_letter+"){0,61}"+t.src_pseudo_letter+"))",t.src_host="(?:(?:(?:(?:"+t.src_domain+")\\.)*"+t.src_domain+"))",t.tpl_host_fuzzy="(?:"+t.src_ip4+"|(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%)))",t.tpl_host_no_ip_fuzzy="(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%))",t.src_host_strict=t.src_host+t.src_host_terminator,t.tpl_host_fuzzy_strict=t.tpl_host_fuzzy+t.src_host_terminator,t.src_host_port_strict=t.src_host+t.src_port+t.src_host_terminator,t.tpl_host_port_fuzzy_strict=t.tpl_host_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_port_no_ip_fuzzy_strict=t.tpl_host_no_ip_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_fuzzy_test="localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:"+t.src_ZPCc+"|>|$))",t.tpl_email_fuzzy='(^|[><|]|"|\\(|'+t.src_ZCc+")("+t.src_email_name+"@"+t.tpl_host_fuzzy_strict+")",t.tpl_link_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_fuzzy_strict+t.src_path+")",t.tpl_link_no_ip_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_no_ip_fuzzy_strict+t.src_path+")",t}},function(e,t){e.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/},function(e,t){e.exports=/[\0-\x1F\x7F-\x9F]/},function(e,t){e.exports=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/},function(e,t){e.exports=/[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/},function(e,t,r){(function(e,n){var s;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(o){t&&t.nodeType,e&&e.nodeType;var i="object"==typeof n&&n;i.global!==i&&i.window!==i&&i.self;var a,u=2147483647,c=/^xn--/,l=/[^\x20-\x7E]/,p=/[\x2E\u3002\uFF0E\uFF61]/g,h={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},f=Math.floor,d=String.fromCharCode;function m(e){throw new RangeError(h[e])}function g(e,t){for(var r=e.length,n=[];r--;)n[r]=t(e[r]);return n}function _(e,t){var r=e.split("@"),n="";return r.length>1&&(n=r[0]+"@",e=r[1]),n+g((e=e.replace(p,".")).split("."),t).join(".")}function b(e){for(var t,r,n=[],s=0,o=e.length;s=55296&&t<=56319&&s65535&&(t+=d((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=d(e)})).join("")}function C(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function v(e,t,r){var n=0;for(e=r?f(e/700):e>>1,e+=f(e/t);e>455;n+=36)e=f(e/35);return f(n+36*e/(e+38))}function D(e){var t,r,n,s,o,i,a,c,l,p,h,d=[],g=e.length,_=0,b=128,C=72;for((r=e.lastIndexOf("-"))<0&&(r=0),n=0;n=128&&m("not-basic"),d.push(e.charCodeAt(n));for(s=r>0?r+1:0;s=g&&m("invalid-input"),((c=(h=e.charCodeAt(s++))-48<10?h-22:h-65<26?h-65:h-97<26?h-97:36)>=36||c>f((u-_)/i))&&m("overflow"),_+=c*i,!(c<(l=a<=C?1:a>=C+26?26:a-C));a+=36)i>f(u/(p=36-l))&&m("overflow"),i*=p;C=v(_-o,t=d.length+1,0==o),f(_/t)>u-b&&m("overflow"),b+=f(_/t),_%=t,d.splice(_++,0,b)}return k(d)}function A(e){var t,r,n,s,o,i,a,c,l,p,h,g,_,k,D,A=[];for(g=(e=b(e)).length,t=128,r=0,o=72,i=0;i=t&&hf((u-r)/(_=n+1))&&m("overflow"),r+=(a-t)*_,t=a,i=0;iu&&m("overflow"),h==t){for(c=r,l=36;!(c<(p=l<=o?1:l>=o+26?26:l-o));l+=36)D=c-p,k=36-p,A.push(d(C(p+D%k,0))),c=f(D/k);A.push(d(C(c,0))),o=v(r,_,n==s),r=0,++n}++r,++t}return A.join("")}a={version:"1.4.1",ucs2:{decode:b,encode:k},decode:D,encode:A,toASCII:function(e){return _(e,(function(e){return l.test(e)?"xn--"+A(e):e}))},toUnicode:function(e){return _(e,(function(e){return c.test(e)?D(e.slice(4).toLowerCase()):e}))}},void 0===(s=function(){return a}.call(t,r,t,e))||(e.exports=s)}()}).call(this,r(69)(e),r(70))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){"use strict";e.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:100},components:{core:{},block:{},inline:{}}}},function(e,t,r){"use strict";e.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["paragraph"]},inline:{rules:["text"],rules2:["balance_pairs","text_collapse"]}}}},function(e,t,r){"use strict";e.exports={options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["blockquote","code","fence","heading","hr","html_block","lheading","list","reference","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","html_inline","image","link","newline","text"],rules2:["balance_pairs","emphasis","text_collapse"]}}}}]); \ No newline at end of file +!function(e){var t={};function r(n){if(t[n])return t[n].exports;var s=t[n]={i:n,l:!1,exports:{}};return e[n].call(s.exports,s,s.exports,r),s.l=!0,s.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)r.d(n,s,function(t){return e[t]}.bind(null,s));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=9)}([function(e,t,r){"use strict";var n=Object.prototype.hasOwnProperty;function s(e,t){return n.call(e,t)}function o(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function i(e){if(e>65535){var t=55296+((e-=65536)>>10),r=56320+(1023&e);return String.fromCharCode(t,r)}return String.fromCharCode(e)}var a=/\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g,u=new RegExp(a.source+"|"+/&([a-z#][a-z0-9]{1,31});/gi.source,"gi"),c=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i,l=r(3);var p=/[&<>"]/,h=/[&<>"]/g,f={"&":"&","<":"<",">":">",'"':"""};function d(e){return f[e]}var m=/[.?*+^$[\]\\(){}|-]/g;var g=r(4);t.lib={},t.lib.mdurl=r(5),t.lib.ucmicro=r(17),t.assign=function(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach((function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach((function(r){e[r]=t[r]}))}})),e},t.isString=function(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)},t.has=s,t.unescapeMd=function(e){return e.indexOf("\\")<0?e:e.replace(a,"$1")},t.unescapeAll=function(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(u,(function(e,t,r){return t||function(e,t){var r=0;return s(l,t)?l[t]:35===t.charCodeAt(0)&&c.test(t)&&o(r="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10))?i(r):e}(e,r)}))},t.isValidEntityCode=o,t.fromCodePoint=i,t.escapeHtml=function(e){return p.test(e)?e.replace(h,d):e},t.arrayReplaceAt=function(e,t,r){return[].concat(e.slice(0,t),r,e.slice(t+1))},t.isSpace=function(e){switch(e){case 9:case 32:return!0}return!1},t.isWhiteSpace=function(e){if(e>=8192&&e<=8202)return!0;switch(e){case 9:case 10:case 11:case 12:case 13:case 32:case 160:case 5760:case 8239:case 8287:case 12288:return!0}return!1},t.isMdAsciiPunct=function(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 124:case 125:case 126:return!0;default:return!1}},t.isPunctChar=function(e){return g.test(e)},t.escapeRE=function(e){return e.replace(m,"\\$&")},t.normalizeReference=function(e){return e=e.trim().replace(/\s+/g," "),"Ṿ"==="ẞ".toLowerCase()&&(e=e.replace(/ẞ/g,"ß")),e.toLowerCase().toUpperCase()}},function(e,t,r){"use strict";function n(){this.__rules__=[],this.__cache__=null}n.prototype.__find__=function(e){for(var t=0;t=0&&(r=this.attrs[t][1]),r},n.prototype.attrJoin=function(e,t){var r=this.attrIndex(e);r<0?this.attrPush([e,t]):this.attrs[r][1]=this.attrs[r][1]+" "+t},e.exports=n},function(e,t,r){"use strict";e.exports=r(12)},function(e,t){e.exports=/[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E49\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD806[\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/},function(e,t,r){"use strict";e.exports.encode=r(13),e.exports.decode=r(14),e.exports.format=r(15),e.exports.parse=r(16)},function(e,t,r){"use strict";var n="<[A-Za-z][A-Za-z0-9\\-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*\\/?>",s="<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>",o=new RegExp("^(?:"+n+"|"+s+"|\x3c!----\x3e|\x3c!--(?:-?[^>-])(?:-?[^-])*--\x3e|<[?][\\s\\S]*?[?]>|]*>|)"),i=new RegExp("^(?:"+n+"|"+s+")");e.exports.HTML_TAG_RE=o,e.exports.HTML_OPEN_CLOSE_TAG_RE=i},function(e,t,r){"use strict";function n(e,t){var r,n,s,o,i,a=[],u=t.length;for(r=0;r=0;r--)95!==(n=t[r]).marker&&42!==n.marker||-1!==n.end&&(s=t[n.end],a=r>0&&t[r-1].end===n.end+1&&t[r-1].token===n.token-1&&t[n.end+1].token===s.token+1&&t[r-1].marker===n.marker,i=String.fromCharCode(n.marker),(o=e.tokens[n.token]).type=a?"strong_open":"em_open",o.tag=a?"strong":"em",o.nesting=1,o.markup=a?i+i:i,o.content="",(o=e.tokens[s.token]).type=a?"strong_close":"em_close",o.tag=a?"strong":"em",o.nesting=-1,o.markup=a?i+i:i,o.content="",a&&(e.tokens[t[r-1].token].content="",e.tokens[t[n.end+1].token].content="",r--))}e.exports.tokenize=function(e,t){var r,n,s=e.pos,o=e.src.charCodeAt(s);if(t)return!1;if(95!==o&&42!==o)return!1;for(n=e.scanDelims(e.pos,42===o),r=0;r{t(e)};acquireNotebookRendererApi("notebookCoreTestRenderer").onDidCreateMarkdown(({element:t,content:r})=>{const n=e.render(r);t.innerHTML=n})}()},function(e,t,r){"use strict";e.exports=r(11)},function(e,t,r){"use strict";var n=r(0),s=r(22),o=r(26),i=r(27),a=r(35),u=r(49),c=r(62),l=r(5),p=r(68),h={default:r(71),zero:r(72),commonmark:r(73)},f=/^(vbscript|javascript|file|data):/,d=/^data:image\/(gif|png|jpeg|webp);/;function m(e){var t=e.trim().toLowerCase();return!f.test(t)||!!d.test(t)}var g=["http:","https:","mailto:"];function _(e){var t=l.parse(e,!0);if(t.hostname&&(!t.protocol||g.indexOf(t.protocol)>=0))try{t.hostname=p.toASCII(t.hostname)}catch(e){}return l.encode(l.format(t))}function b(e){var t=l.parse(e,!0);if(t.hostname&&(!t.protocol||g.indexOf(t.protocol)>=0))try{t.hostname=p.toUnicode(t.hostname)}catch(e){}return l.decode(l.format(t),l.decode.defaultChars+"%")}function k(e,t){if(!(this instanceof k))return new k(e,t);t||n.isString(e)||(t=e||{},e="default"),this.inline=new u,this.block=new a,this.core=new i,this.renderer=new o,this.linkify=new c,this.validateLink=m,this.normalizeLink=_,this.normalizeLinkText=b,this.utils=n,this.helpers=n.assign({},s),this.options={},this.configure(e),t&&this.set(t)}k.prototype.set=function(e){return n.assign(this.options,e),this},k.prototype.configure=function(e){var t,r=this;if(n.isString(e)&&!(e=h[t=e]))throw new Error('Wrong `markdown-it` preset "'+t+'", check name');if(!e)throw new Error("Wrong `markdown-it` preset, can't be empty");return e.options&&r.set(e.options),e.components&&Object.keys(e.components).forEach((function(t){e.components[t].rules&&r[t].ruler.enableOnly(e.components[t].rules),e.components[t].rules2&&r[t].ruler2.enableOnly(e.components[t].rules2)})),this},k.prototype.enable=function(e,t){var r=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){r=r.concat(this[t].ruler.enable(e,!0))}),this),r=r.concat(this.inline.ruler2.enable(e,!0));var n=e.filter((function(e){return r.indexOf(e)<0}));if(n.length&&!t)throw new Error("MarkdownIt. Failed to enable unknown rule(s): "+n);return this},k.prototype.disable=function(e,t){var r=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach((function(t){r=r.concat(this[t].ruler.disable(e,!0))}),this),r=r.concat(this.inline.ruler2.disable(e,!0));var n=e.filter((function(e){return r.indexOf(e)<0}));if(n.length&&!t)throw new Error("MarkdownIt. Failed to disable unknown rule(s): "+n);return this},k.prototype.use=function(e){var t=[this].concat(Array.prototype.slice.call(arguments,1));return e.apply(e,t),this},k.prototype.parse=function(e,t){if("string"!=typeof e)throw new Error("Input data should be a String");var r=new this.core.State(e,this,t);return this.core.process(r),r.tokens},k.prototype.render=function(e,t){return t=t||{},this.renderer.render(this.parse(e,t),this.options,t)},k.prototype.parseInline=function(e,t){var r=new this.core.State(e,this,t);return r.inlineMode=!0,this.core.process(r),r.tokens},k.prototype.renderInline=function(e,t){return t=t||{},this.renderer.render(this.parseInline(e,t),this.options,t)},e.exports=k},function(e){e.exports=JSON.parse('{"Aacute":"Á","aacute":"á","Abreve":"Ă","abreve":"ă","ac":"∾","acd":"∿","acE":"∾̳","Acirc":"Â","acirc":"â","acute":"´","Acy":"А","acy":"а","AElig":"Æ","aelig":"æ","af":"⁡","Afr":"𝔄","afr":"𝔞","Agrave":"À","agrave":"à","alefsym":"ℵ","aleph":"ℵ","Alpha":"Α","alpha":"α","Amacr":"Ā","amacr":"ā","amalg":"⨿","amp":"&","AMP":"&","andand":"⩕","And":"⩓","and":"∧","andd":"⩜","andslope":"⩘","andv":"⩚","ang":"∠","ange":"⦤","angle":"∠","angmsdaa":"⦨","angmsdab":"⦩","angmsdac":"⦪","angmsdad":"⦫","angmsdae":"⦬","angmsdaf":"⦭","angmsdag":"⦮","angmsdah":"⦯","angmsd":"∡","angrt":"∟","angrtvb":"⊾","angrtvbd":"⦝","angsph":"∢","angst":"Å","angzarr":"⍼","Aogon":"Ą","aogon":"ą","Aopf":"𝔸","aopf":"𝕒","apacir":"⩯","ap":"≈","apE":"⩰","ape":"≊","apid":"≋","apos":"\'","ApplyFunction":"⁡","approx":"≈","approxeq":"≊","Aring":"Å","aring":"å","Ascr":"𝒜","ascr":"𝒶","Assign":"≔","ast":"*","asymp":"≈","asympeq":"≍","Atilde":"Ã","atilde":"ã","Auml":"Ä","auml":"ä","awconint":"∳","awint":"⨑","backcong":"≌","backepsilon":"϶","backprime":"‵","backsim":"∽","backsimeq":"⋍","Backslash":"∖","Barv":"⫧","barvee":"⊽","barwed":"⌅","Barwed":"⌆","barwedge":"⌅","bbrk":"⎵","bbrktbrk":"⎶","bcong":"≌","Bcy":"Б","bcy":"б","bdquo":"„","becaus":"∵","because":"∵","Because":"∵","bemptyv":"⦰","bepsi":"϶","bernou":"ℬ","Bernoullis":"ℬ","Beta":"Β","beta":"β","beth":"ℶ","between":"≬","Bfr":"𝔅","bfr":"𝔟","bigcap":"⋂","bigcirc":"◯","bigcup":"⋃","bigodot":"⨀","bigoplus":"⨁","bigotimes":"⨂","bigsqcup":"⨆","bigstar":"★","bigtriangledown":"▽","bigtriangleup":"△","biguplus":"⨄","bigvee":"⋁","bigwedge":"⋀","bkarow":"⤍","blacklozenge":"⧫","blacksquare":"▪","blacktriangle":"▴","blacktriangledown":"▾","blacktriangleleft":"◂","blacktriangleright":"▸","blank":"␣","blk12":"▒","blk14":"░","blk34":"▓","block":"█","bne":"=⃥","bnequiv":"≡⃥","bNot":"⫭","bnot":"⌐","Bopf":"𝔹","bopf":"𝕓","bot":"⊥","bottom":"⊥","bowtie":"⋈","boxbox":"⧉","boxdl":"┐","boxdL":"╕","boxDl":"╖","boxDL":"╗","boxdr":"┌","boxdR":"╒","boxDr":"╓","boxDR":"╔","boxh":"─","boxH":"═","boxhd":"┬","boxHd":"╤","boxhD":"╥","boxHD":"╦","boxhu":"┴","boxHu":"╧","boxhU":"╨","boxHU":"╩","boxminus":"⊟","boxplus":"⊞","boxtimes":"⊠","boxul":"┘","boxuL":"╛","boxUl":"╜","boxUL":"╝","boxur":"└","boxuR":"╘","boxUr":"╙","boxUR":"╚","boxv":"│","boxV":"║","boxvh":"┼","boxvH":"╪","boxVh":"╫","boxVH":"╬","boxvl":"┤","boxvL":"╡","boxVl":"╢","boxVL":"╣","boxvr":"├","boxvR":"╞","boxVr":"╟","boxVR":"╠","bprime":"‵","breve":"˘","Breve":"˘","brvbar":"¦","bscr":"𝒷","Bscr":"ℬ","bsemi":"⁏","bsim":"∽","bsime":"⋍","bsolb":"⧅","bsol":"\\\\","bsolhsub":"⟈","bull":"•","bullet":"•","bump":"≎","bumpE":"⪮","bumpe":"≏","Bumpeq":"≎","bumpeq":"≏","Cacute":"Ć","cacute":"ć","capand":"⩄","capbrcup":"⩉","capcap":"⩋","cap":"∩","Cap":"⋒","capcup":"⩇","capdot":"⩀","CapitalDifferentialD":"ⅅ","caps":"∩︀","caret":"⁁","caron":"ˇ","Cayleys":"ℭ","ccaps":"⩍","Ccaron":"Č","ccaron":"č","Ccedil":"Ç","ccedil":"ç","Ccirc":"Ĉ","ccirc":"ĉ","Cconint":"∰","ccups":"⩌","ccupssm":"⩐","Cdot":"Ċ","cdot":"ċ","cedil":"¸","Cedilla":"¸","cemptyv":"⦲","cent":"¢","centerdot":"·","CenterDot":"·","cfr":"𝔠","Cfr":"ℭ","CHcy":"Ч","chcy":"ч","check":"✓","checkmark":"✓","Chi":"Χ","chi":"χ","circ":"ˆ","circeq":"≗","circlearrowleft":"↺","circlearrowright":"↻","circledast":"⊛","circledcirc":"⊚","circleddash":"⊝","CircleDot":"⊙","circledR":"®","circledS":"Ⓢ","CircleMinus":"⊖","CirclePlus":"⊕","CircleTimes":"⊗","cir":"○","cirE":"⧃","cire":"≗","cirfnint":"⨐","cirmid":"⫯","cirscir":"⧂","ClockwiseContourIntegral":"∲","CloseCurlyDoubleQuote":"”","CloseCurlyQuote":"’","clubs":"♣","clubsuit":"♣","colon":":","Colon":"∷","Colone":"⩴","colone":"≔","coloneq":"≔","comma":",","commat":"@","comp":"∁","compfn":"∘","complement":"∁","complexes":"ℂ","cong":"≅","congdot":"⩭","Congruent":"≡","conint":"∮","Conint":"∯","ContourIntegral":"∮","copf":"𝕔","Copf":"ℂ","coprod":"∐","Coproduct":"∐","copy":"©","COPY":"©","copysr":"℗","CounterClockwiseContourIntegral":"∳","crarr":"↵","cross":"✗","Cross":"⨯","Cscr":"𝒞","cscr":"𝒸","csub":"⫏","csube":"⫑","csup":"⫐","csupe":"⫒","ctdot":"⋯","cudarrl":"⤸","cudarrr":"⤵","cuepr":"⋞","cuesc":"⋟","cularr":"↶","cularrp":"⤽","cupbrcap":"⩈","cupcap":"⩆","CupCap":"≍","cup":"∪","Cup":"⋓","cupcup":"⩊","cupdot":"⊍","cupor":"⩅","cups":"∪︀","curarr":"↷","curarrm":"⤼","curlyeqprec":"⋞","curlyeqsucc":"⋟","curlyvee":"⋎","curlywedge":"⋏","curren":"¤","curvearrowleft":"↶","curvearrowright":"↷","cuvee":"⋎","cuwed":"⋏","cwconint":"∲","cwint":"∱","cylcty":"⌭","dagger":"†","Dagger":"‡","daleth":"ℸ","darr":"↓","Darr":"↡","dArr":"⇓","dash":"‐","Dashv":"⫤","dashv":"⊣","dbkarow":"⤏","dblac":"˝","Dcaron":"Ď","dcaron":"ď","Dcy":"Д","dcy":"д","ddagger":"‡","ddarr":"⇊","DD":"ⅅ","dd":"ⅆ","DDotrahd":"⤑","ddotseq":"⩷","deg":"°","Del":"∇","Delta":"Δ","delta":"δ","demptyv":"⦱","dfisht":"⥿","Dfr":"𝔇","dfr":"𝔡","dHar":"⥥","dharl":"⇃","dharr":"⇂","DiacriticalAcute":"´","DiacriticalDot":"˙","DiacriticalDoubleAcute":"˝","DiacriticalGrave":"`","DiacriticalTilde":"˜","diam":"⋄","diamond":"⋄","Diamond":"⋄","diamondsuit":"♦","diams":"♦","die":"¨","DifferentialD":"ⅆ","digamma":"ϝ","disin":"⋲","div":"÷","divide":"÷","divideontimes":"⋇","divonx":"⋇","DJcy":"Ђ","djcy":"ђ","dlcorn":"⌞","dlcrop":"⌍","dollar":"$","Dopf":"𝔻","dopf":"𝕕","Dot":"¨","dot":"˙","DotDot":"⃜","doteq":"≐","doteqdot":"≑","DotEqual":"≐","dotminus":"∸","dotplus":"∔","dotsquare":"⊡","doublebarwedge":"⌆","DoubleContourIntegral":"∯","DoubleDot":"¨","DoubleDownArrow":"⇓","DoubleLeftArrow":"⇐","DoubleLeftRightArrow":"⇔","DoubleLeftTee":"⫤","DoubleLongLeftArrow":"⟸","DoubleLongLeftRightArrow":"⟺","DoubleLongRightArrow":"⟹","DoubleRightArrow":"⇒","DoubleRightTee":"⊨","DoubleUpArrow":"⇑","DoubleUpDownArrow":"⇕","DoubleVerticalBar":"∥","DownArrowBar":"⤓","downarrow":"↓","DownArrow":"↓","Downarrow":"⇓","DownArrowUpArrow":"⇵","DownBreve":"̑","downdownarrows":"⇊","downharpoonleft":"⇃","downharpoonright":"⇂","DownLeftRightVector":"⥐","DownLeftTeeVector":"⥞","DownLeftVectorBar":"⥖","DownLeftVector":"↽","DownRightTeeVector":"⥟","DownRightVectorBar":"⥗","DownRightVector":"⇁","DownTeeArrow":"↧","DownTee":"⊤","drbkarow":"⤐","drcorn":"⌟","drcrop":"⌌","Dscr":"𝒟","dscr":"𝒹","DScy":"Ѕ","dscy":"ѕ","dsol":"⧶","Dstrok":"Đ","dstrok":"đ","dtdot":"⋱","dtri":"▿","dtrif":"▾","duarr":"⇵","duhar":"⥯","dwangle":"⦦","DZcy":"Џ","dzcy":"џ","dzigrarr":"⟿","Eacute":"É","eacute":"é","easter":"⩮","Ecaron":"Ě","ecaron":"ě","Ecirc":"Ê","ecirc":"ê","ecir":"≖","ecolon":"≕","Ecy":"Э","ecy":"э","eDDot":"⩷","Edot":"Ė","edot":"ė","eDot":"≑","ee":"ⅇ","efDot":"≒","Efr":"𝔈","efr":"𝔢","eg":"⪚","Egrave":"È","egrave":"è","egs":"⪖","egsdot":"⪘","el":"⪙","Element":"∈","elinters":"⏧","ell":"ℓ","els":"⪕","elsdot":"⪗","Emacr":"Ē","emacr":"ē","empty":"∅","emptyset":"∅","EmptySmallSquare":"◻","emptyv":"∅","EmptyVerySmallSquare":"▫","emsp13":" ","emsp14":" ","emsp":" ","ENG":"Ŋ","eng":"ŋ","ensp":" ","Eogon":"Ę","eogon":"ę","Eopf":"𝔼","eopf":"𝕖","epar":"⋕","eparsl":"⧣","eplus":"⩱","epsi":"ε","Epsilon":"Ε","epsilon":"ε","epsiv":"ϵ","eqcirc":"≖","eqcolon":"≕","eqsim":"≂","eqslantgtr":"⪖","eqslantless":"⪕","Equal":"⩵","equals":"=","EqualTilde":"≂","equest":"≟","Equilibrium":"⇌","equiv":"≡","equivDD":"⩸","eqvparsl":"⧥","erarr":"⥱","erDot":"≓","escr":"ℯ","Escr":"ℰ","esdot":"≐","Esim":"⩳","esim":"≂","Eta":"Η","eta":"η","ETH":"Ð","eth":"ð","Euml":"Ë","euml":"ë","euro":"€","excl":"!","exist":"∃","Exists":"∃","expectation":"ℰ","exponentiale":"ⅇ","ExponentialE":"ⅇ","fallingdotseq":"≒","Fcy":"Ф","fcy":"ф","female":"♀","ffilig":"ffi","fflig":"ff","ffllig":"ffl","Ffr":"𝔉","ffr":"𝔣","filig":"fi","FilledSmallSquare":"◼","FilledVerySmallSquare":"▪","fjlig":"fj","flat":"♭","fllig":"fl","fltns":"▱","fnof":"ƒ","Fopf":"𝔽","fopf":"𝕗","forall":"∀","ForAll":"∀","fork":"⋔","forkv":"⫙","Fouriertrf":"ℱ","fpartint":"⨍","frac12":"½","frac13":"⅓","frac14":"¼","frac15":"⅕","frac16":"⅙","frac18":"⅛","frac23":"⅔","frac25":"⅖","frac34":"¾","frac35":"⅗","frac38":"⅜","frac45":"⅘","frac56":"⅚","frac58":"⅝","frac78":"⅞","frasl":"⁄","frown":"⌢","fscr":"𝒻","Fscr":"ℱ","gacute":"ǵ","Gamma":"Γ","gamma":"γ","Gammad":"Ϝ","gammad":"ϝ","gap":"⪆","Gbreve":"Ğ","gbreve":"ğ","Gcedil":"Ģ","Gcirc":"Ĝ","gcirc":"ĝ","Gcy":"Г","gcy":"г","Gdot":"Ġ","gdot":"ġ","ge":"≥","gE":"≧","gEl":"⪌","gel":"⋛","geq":"≥","geqq":"≧","geqslant":"⩾","gescc":"⪩","ges":"⩾","gesdot":"⪀","gesdoto":"⪂","gesdotol":"⪄","gesl":"⋛︀","gesles":"⪔","Gfr":"𝔊","gfr":"𝔤","gg":"≫","Gg":"⋙","ggg":"⋙","gimel":"ℷ","GJcy":"Ѓ","gjcy":"ѓ","gla":"⪥","gl":"≷","glE":"⪒","glj":"⪤","gnap":"⪊","gnapprox":"⪊","gne":"⪈","gnE":"≩","gneq":"⪈","gneqq":"≩","gnsim":"⋧","Gopf":"𝔾","gopf":"𝕘","grave":"`","GreaterEqual":"≥","GreaterEqualLess":"⋛","GreaterFullEqual":"≧","GreaterGreater":"⪢","GreaterLess":"≷","GreaterSlantEqual":"⩾","GreaterTilde":"≳","Gscr":"𝒢","gscr":"ℊ","gsim":"≳","gsime":"⪎","gsiml":"⪐","gtcc":"⪧","gtcir":"⩺","gt":">","GT":">","Gt":"≫","gtdot":"⋗","gtlPar":"⦕","gtquest":"⩼","gtrapprox":"⪆","gtrarr":"⥸","gtrdot":"⋗","gtreqless":"⋛","gtreqqless":"⪌","gtrless":"≷","gtrsim":"≳","gvertneqq":"≩︀","gvnE":"≩︀","Hacek":"ˇ","hairsp":" ","half":"½","hamilt":"ℋ","HARDcy":"Ъ","hardcy":"ъ","harrcir":"⥈","harr":"↔","hArr":"⇔","harrw":"↭","Hat":"^","hbar":"ℏ","Hcirc":"Ĥ","hcirc":"ĥ","hearts":"♥","heartsuit":"♥","hellip":"…","hercon":"⊹","hfr":"𝔥","Hfr":"ℌ","HilbertSpace":"ℋ","hksearow":"⤥","hkswarow":"⤦","hoarr":"⇿","homtht":"∻","hookleftarrow":"↩","hookrightarrow":"↪","hopf":"𝕙","Hopf":"ℍ","horbar":"―","HorizontalLine":"─","hscr":"𝒽","Hscr":"ℋ","hslash":"ℏ","Hstrok":"Ħ","hstrok":"ħ","HumpDownHump":"≎","HumpEqual":"≏","hybull":"⁃","hyphen":"‐","Iacute":"Í","iacute":"í","ic":"⁣","Icirc":"Î","icirc":"î","Icy":"И","icy":"и","Idot":"İ","IEcy":"Е","iecy":"е","iexcl":"¡","iff":"⇔","ifr":"𝔦","Ifr":"ℑ","Igrave":"Ì","igrave":"ì","ii":"ⅈ","iiiint":"⨌","iiint":"∭","iinfin":"⧜","iiota":"℩","IJlig":"IJ","ijlig":"ij","Imacr":"Ī","imacr":"ī","image":"ℑ","ImaginaryI":"ⅈ","imagline":"ℐ","imagpart":"ℑ","imath":"ı","Im":"ℑ","imof":"⊷","imped":"Ƶ","Implies":"⇒","incare":"℅","in":"∈","infin":"∞","infintie":"⧝","inodot":"ı","intcal":"⊺","int":"∫","Int":"∬","integers":"ℤ","Integral":"∫","intercal":"⊺","Intersection":"⋂","intlarhk":"⨗","intprod":"⨼","InvisibleComma":"⁣","InvisibleTimes":"⁢","IOcy":"Ё","iocy":"ё","Iogon":"Į","iogon":"į","Iopf":"𝕀","iopf":"𝕚","Iota":"Ι","iota":"ι","iprod":"⨼","iquest":"¿","iscr":"𝒾","Iscr":"ℐ","isin":"∈","isindot":"⋵","isinE":"⋹","isins":"⋴","isinsv":"⋳","isinv":"∈","it":"⁢","Itilde":"Ĩ","itilde":"ĩ","Iukcy":"І","iukcy":"і","Iuml":"Ï","iuml":"ï","Jcirc":"Ĵ","jcirc":"ĵ","Jcy":"Й","jcy":"й","Jfr":"𝔍","jfr":"𝔧","jmath":"ȷ","Jopf":"𝕁","jopf":"𝕛","Jscr":"𝒥","jscr":"𝒿","Jsercy":"Ј","jsercy":"ј","Jukcy":"Є","jukcy":"є","Kappa":"Κ","kappa":"κ","kappav":"ϰ","Kcedil":"Ķ","kcedil":"ķ","Kcy":"К","kcy":"к","Kfr":"𝔎","kfr":"𝔨","kgreen":"ĸ","KHcy":"Х","khcy":"х","KJcy":"Ќ","kjcy":"ќ","Kopf":"𝕂","kopf":"𝕜","Kscr":"𝒦","kscr":"𝓀","lAarr":"⇚","Lacute":"Ĺ","lacute":"ĺ","laemptyv":"⦴","lagran":"ℒ","Lambda":"Λ","lambda":"λ","lang":"⟨","Lang":"⟪","langd":"⦑","langle":"⟨","lap":"⪅","Laplacetrf":"ℒ","laquo":"«","larrb":"⇤","larrbfs":"⤟","larr":"←","Larr":"↞","lArr":"⇐","larrfs":"⤝","larrhk":"↩","larrlp":"↫","larrpl":"⤹","larrsim":"⥳","larrtl":"↢","latail":"⤙","lAtail":"⤛","lat":"⪫","late":"⪭","lates":"⪭︀","lbarr":"⤌","lBarr":"⤎","lbbrk":"❲","lbrace":"{","lbrack":"[","lbrke":"⦋","lbrksld":"⦏","lbrkslu":"⦍","Lcaron":"Ľ","lcaron":"ľ","Lcedil":"Ļ","lcedil":"ļ","lceil":"⌈","lcub":"{","Lcy":"Л","lcy":"л","ldca":"⤶","ldquo":"“","ldquor":"„","ldrdhar":"⥧","ldrushar":"⥋","ldsh":"↲","le":"≤","lE":"≦","LeftAngleBracket":"⟨","LeftArrowBar":"⇤","leftarrow":"←","LeftArrow":"←","Leftarrow":"⇐","LeftArrowRightArrow":"⇆","leftarrowtail":"↢","LeftCeiling":"⌈","LeftDoubleBracket":"⟦","LeftDownTeeVector":"⥡","LeftDownVectorBar":"⥙","LeftDownVector":"⇃","LeftFloor":"⌊","leftharpoondown":"↽","leftharpoonup":"↼","leftleftarrows":"⇇","leftrightarrow":"↔","LeftRightArrow":"↔","Leftrightarrow":"⇔","leftrightarrows":"⇆","leftrightharpoons":"⇋","leftrightsquigarrow":"↭","LeftRightVector":"⥎","LeftTeeArrow":"↤","LeftTee":"⊣","LeftTeeVector":"⥚","leftthreetimes":"⋋","LeftTriangleBar":"⧏","LeftTriangle":"⊲","LeftTriangleEqual":"⊴","LeftUpDownVector":"⥑","LeftUpTeeVector":"⥠","LeftUpVectorBar":"⥘","LeftUpVector":"↿","LeftVectorBar":"⥒","LeftVector":"↼","lEg":"⪋","leg":"⋚","leq":"≤","leqq":"≦","leqslant":"⩽","lescc":"⪨","les":"⩽","lesdot":"⩿","lesdoto":"⪁","lesdotor":"⪃","lesg":"⋚︀","lesges":"⪓","lessapprox":"⪅","lessdot":"⋖","lesseqgtr":"⋚","lesseqqgtr":"⪋","LessEqualGreater":"⋚","LessFullEqual":"≦","LessGreater":"≶","lessgtr":"≶","LessLess":"⪡","lesssim":"≲","LessSlantEqual":"⩽","LessTilde":"≲","lfisht":"⥼","lfloor":"⌊","Lfr":"𝔏","lfr":"𝔩","lg":"≶","lgE":"⪑","lHar":"⥢","lhard":"↽","lharu":"↼","lharul":"⥪","lhblk":"▄","LJcy":"Љ","ljcy":"љ","llarr":"⇇","ll":"≪","Ll":"⋘","llcorner":"⌞","Lleftarrow":"⇚","llhard":"⥫","lltri":"◺","Lmidot":"Ŀ","lmidot":"ŀ","lmoustache":"⎰","lmoust":"⎰","lnap":"⪉","lnapprox":"⪉","lne":"⪇","lnE":"≨","lneq":"⪇","lneqq":"≨","lnsim":"⋦","loang":"⟬","loarr":"⇽","lobrk":"⟦","longleftarrow":"⟵","LongLeftArrow":"⟵","Longleftarrow":"⟸","longleftrightarrow":"⟷","LongLeftRightArrow":"⟷","Longleftrightarrow":"⟺","longmapsto":"⟼","longrightarrow":"⟶","LongRightArrow":"⟶","Longrightarrow":"⟹","looparrowleft":"↫","looparrowright":"↬","lopar":"⦅","Lopf":"𝕃","lopf":"𝕝","loplus":"⨭","lotimes":"⨴","lowast":"∗","lowbar":"_","LowerLeftArrow":"↙","LowerRightArrow":"↘","loz":"◊","lozenge":"◊","lozf":"⧫","lpar":"(","lparlt":"⦓","lrarr":"⇆","lrcorner":"⌟","lrhar":"⇋","lrhard":"⥭","lrm":"‎","lrtri":"⊿","lsaquo":"‹","lscr":"𝓁","Lscr":"ℒ","lsh":"↰","Lsh":"↰","lsim":"≲","lsime":"⪍","lsimg":"⪏","lsqb":"[","lsquo":"‘","lsquor":"‚","Lstrok":"Ł","lstrok":"ł","ltcc":"⪦","ltcir":"⩹","lt":"<","LT":"<","Lt":"≪","ltdot":"⋖","lthree":"⋋","ltimes":"⋉","ltlarr":"⥶","ltquest":"⩻","ltri":"◃","ltrie":"⊴","ltrif":"◂","ltrPar":"⦖","lurdshar":"⥊","luruhar":"⥦","lvertneqq":"≨︀","lvnE":"≨︀","macr":"¯","male":"♂","malt":"✠","maltese":"✠","Map":"⤅","map":"↦","mapsto":"↦","mapstodown":"↧","mapstoleft":"↤","mapstoup":"↥","marker":"▮","mcomma":"⨩","Mcy":"М","mcy":"м","mdash":"—","mDDot":"∺","measuredangle":"∡","MediumSpace":" ","Mellintrf":"ℳ","Mfr":"𝔐","mfr":"𝔪","mho":"℧","micro":"µ","midast":"*","midcir":"⫰","mid":"∣","middot":"·","minusb":"⊟","minus":"−","minusd":"∸","minusdu":"⨪","MinusPlus":"∓","mlcp":"⫛","mldr":"…","mnplus":"∓","models":"⊧","Mopf":"𝕄","mopf":"𝕞","mp":"∓","mscr":"𝓂","Mscr":"ℳ","mstpos":"∾","Mu":"Μ","mu":"μ","multimap":"⊸","mumap":"⊸","nabla":"∇","Nacute":"Ń","nacute":"ń","nang":"∠⃒","nap":"≉","napE":"⩰̸","napid":"≋̸","napos":"ʼn","napprox":"≉","natural":"♮","naturals":"ℕ","natur":"♮","nbsp":" ","nbump":"≎̸","nbumpe":"≏̸","ncap":"⩃","Ncaron":"Ň","ncaron":"ň","Ncedil":"Ņ","ncedil":"ņ","ncong":"≇","ncongdot":"⩭̸","ncup":"⩂","Ncy":"Н","ncy":"н","ndash":"–","nearhk":"⤤","nearr":"↗","neArr":"⇗","nearrow":"↗","ne":"≠","nedot":"≐̸","NegativeMediumSpace":"​","NegativeThickSpace":"​","NegativeThinSpace":"​","NegativeVeryThinSpace":"​","nequiv":"≢","nesear":"⤨","nesim":"≂̸","NestedGreaterGreater":"≫","NestedLessLess":"≪","NewLine":"\\n","nexist":"∄","nexists":"∄","Nfr":"𝔑","nfr":"𝔫","ngE":"≧̸","nge":"≱","ngeq":"≱","ngeqq":"≧̸","ngeqslant":"⩾̸","nges":"⩾̸","nGg":"⋙̸","ngsim":"≵","nGt":"≫⃒","ngt":"≯","ngtr":"≯","nGtv":"≫̸","nharr":"↮","nhArr":"⇎","nhpar":"⫲","ni":"∋","nis":"⋼","nisd":"⋺","niv":"∋","NJcy":"Њ","njcy":"њ","nlarr":"↚","nlArr":"⇍","nldr":"‥","nlE":"≦̸","nle":"≰","nleftarrow":"↚","nLeftarrow":"⇍","nleftrightarrow":"↮","nLeftrightarrow":"⇎","nleq":"≰","nleqq":"≦̸","nleqslant":"⩽̸","nles":"⩽̸","nless":"≮","nLl":"⋘̸","nlsim":"≴","nLt":"≪⃒","nlt":"≮","nltri":"⋪","nltrie":"⋬","nLtv":"≪̸","nmid":"∤","NoBreak":"⁠","NonBreakingSpace":" ","nopf":"𝕟","Nopf":"ℕ","Not":"⫬","not":"¬","NotCongruent":"≢","NotCupCap":"≭","NotDoubleVerticalBar":"∦","NotElement":"∉","NotEqual":"≠","NotEqualTilde":"≂̸","NotExists":"∄","NotGreater":"≯","NotGreaterEqual":"≱","NotGreaterFullEqual":"≧̸","NotGreaterGreater":"≫̸","NotGreaterLess":"≹","NotGreaterSlantEqual":"⩾̸","NotGreaterTilde":"≵","NotHumpDownHump":"≎̸","NotHumpEqual":"≏̸","notin":"∉","notindot":"⋵̸","notinE":"⋹̸","notinva":"∉","notinvb":"⋷","notinvc":"⋶","NotLeftTriangleBar":"⧏̸","NotLeftTriangle":"⋪","NotLeftTriangleEqual":"⋬","NotLess":"≮","NotLessEqual":"≰","NotLessGreater":"≸","NotLessLess":"≪̸","NotLessSlantEqual":"⩽̸","NotLessTilde":"≴","NotNestedGreaterGreater":"⪢̸","NotNestedLessLess":"⪡̸","notni":"∌","notniva":"∌","notnivb":"⋾","notnivc":"⋽","NotPrecedes":"⊀","NotPrecedesEqual":"⪯̸","NotPrecedesSlantEqual":"⋠","NotReverseElement":"∌","NotRightTriangleBar":"⧐̸","NotRightTriangle":"⋫","NotRightTriangleEqual":"⋭","NotSquareSubset":"⊏̸","NotSquareSubsetEqual":"⋢","NotSquareSuperset":"⊐̸","NotSquareSupersetEqual":"⋣","NotSubset":"⊂⃒","NotSubsetEqual":"⊈","NotSucceeds":"⊁","NotSucceedsEqual":"⪰̸","NotSucceedsSlantEqual":"⋡","NotSucceedsTilde":"≿̸","NotSuperset":"⊃⃒","NotSupersetEqual":"⊉","NotTilde":"≁","NotTildeEqual":"≄","NotTildeFullEqual":"≇","NotTildeTilde":"≉","NotVerticalBar":"∤","nparallel":"∦","npar":"∦","nparsl":"⫽⃥","npart":"∂̸","npolint":"⨔","npr":"⊀","nprcue":"⋠","nprec":"⊀","npreceq":"⪯̸","npre":"⪯̸","nrarrc":"⤳̸","nrarr":"↛","nrArr":"⇏","nrarrw":"↝̸","nrightarrow":"↛","nRightarrow":"⇏","nrtri":"⋫","nrtrie":"⋭","nsc":"⊁","nsccue":"⋡","nsce":"⪰̸","Nscr":"𝒩","nscr":"𝓃","nshortmid":"∤","nshortparallel":"∦","nsim":"≁","nsime":"≄","nsimeq":"≄","nsmid":"∤","nspar":"∦","nsqsube":"⋢","nsqsupe":"⋣","nsub":"⊄","nsubE":"⫅̸","nsube":"⊈","nsubset":"⊂⃒","nsubseteq":"⊈","nsubseteqq":"⫅̸","nsucc":"⊁","nsucceq":"⪰̸","nsup":"⊅","nsupE":"⫆̸","nsupe":"⊉","nsupset":"⊃⃒","nsupseteq":"⊉","nsupseteqq":"⫆̸","ntgl":"≹","Ntilde":"Ñ","ntilde":"ñ","ntlg":"≸","ntriangleleft":"⋪","ntrianglelefteq":"⋬","ntriangleright":"⋫","ntrianglerighteq":"⋭","Nu":"Ν","nu":"ν","num":"#","numero":"№","numsp":" ","nvap":"≍⃒","nvdash":"⊬","nvDash":"⊭","nVdash":"⊮","nVDash":"⊯","nvge":"≥⃒","nvgt":">⃒","nvHarr":"⤄","nvinfin":"⧞","nvlArr":"⤂","nvle":"≤⃒","nvlt":"<⃒","nvltrie":"⊴⃒","nvrArr":"⤃","nvrtrie":"⊵⃒","nvsim":"∼⃒","nwarhk":"⤣","nwarr":"↖","nwArr":"⇖","nwarrow":"↖","nwnear":"⤧","Oacute":"Ó","oacute":"ó","oast":"⊛","Ocirc":"Ô","ocirc":"ô","ocir":"⊚","Ocy":"О","ocy":"о","odash":"⊝","Odblac":"Ő","odblac":"ő","odiv":"⨸","odot":"⊙","odsold":"⦼","OElig":"Œ","oelig":"œ","ofcir":"⦿","Ofr":"𝔒","ofr":"𝔬","ogon":"˛","Ograve":"Ò","ograve":"ò","ogt":"⧁","ohbar":"⦵","ohm":"Ω","oint":"∮","olarr":"↺","olcir":"⦾","olcross":"⦻","oline":"‾","olt":"⧀","Omacr":"Ō","omacr":"ō","Omega":"Ω","omega":"ω","Omicron":"Ο","omicron":"ο","omid":"⦶","ominus":"⊖","Oopf":"𝕆","oopf":"𝕠","opar":"⦷","OpenCurlyDoubleQuote":"“","OpenCurlyQuote":"‘","operp":"⦹","oplus":"⊕","orarr":"↻","Or":"⩔","or":"∨","ord":"⩝","order":"ℴ","orderof":"ℴ","ordf":"ª","ordm":"º","origof":"⊶","oror":"⩖","orslope":"⩗","orv":"⩛","oS":"Ⓢ","Oscr":"𝒪","oscr":"ℴ","Oslash":"Ø","oslash":"ø","osol":"⊘","Otilde":"Õ","otilde":"õ","otimesas":"⨶","Otimes":"⨷","otimes":"⊗","Ouml":"Ö","ouml":"ö","ovbar":"⌽","OverBar":"‾","OverBrace":"⏞","OverBracket":"⎴","OverParenthesis":"⏜","para":"¶","parallel":"∥","par":"∥","parsim":"⫳","parsl":"⫽","part":"∂","PartialD":"∂","Pcy":"П","pcy":"п","percnt":"%","period":".","permil":"‰","perp":"⊥","pertenk":"‱","Pfr":"𝔓","pfr":"𝔭","Phi":"Φ","phi":"φ","phiv":"ϕ","phmmat":"ℳ","phone":"☎","Pi":"Π","pi":"π","pitchfork":"⋔","piv":"ϖ","planck":"ℏ","planckh":"ℎ","plankv":"ℏ","plusacir":"⨣","plusb":"⊞","pluscir":"⨢","plus":"+","plusdo":"∔","plusdu":"⨥","pluse":"⩲","PlusMinus":"±","plusmn":"±","plussim":"⨦","plustwo":"⨧","pm":"±","Poincareplane":"ℌ","pointint":"⨕","popf":"𝕡","Popf":"ℙ","pound":"£","prap":"⪷","Pr":"⪻","pr":"≺","prcue":"≼","precapprox":"⪷","prec":"≺","preccurlyeq":"≼","Precedes":"≺","PrecedesEqual":"⪯","PrecedesSlantEqual":"≼","PrecedesTilde":"≾","preceq":"⪯","precnapprox":"⪹","precneqq":"⪵","precnsim":"⋨","pre":"⪯","prE":"⪳","precsim":"≾","prime":"′","Prime":"″","primes":"ℙ","prnap":"⪹","prnE":"⪵","prnsim":"⋨","prod":"∏","Product":"∏","profalar":"⌮","profline":"⌒","profsurf":"⌓","prop":"∝","Proportional":"∝","Proportion":"∷","propto":"∝","prsim":"≾","prurel":"⊰","Pscr":"𝒫","pscr":"𝓅","Psi":"Ψ","psi":"ψ","puncsp":" ","Qfr":"𝔔","qfr":"𝔮","qint":"⨌","qopf":"𝕢","Qopf":"ℚ","qprime":"⁗","Qscr":"𝒬","qscr":"𝓆","quaternions":"ℍ","quatint":"⨖","quest":"?","questeq":"≟","quot":"\\"","QUOT":"\\"","rAarr":"⇛","race":"∽̱","Racute":"Ŕ","racute":"ŕ","radic":"√","raemptyv":"⦳","rang":"⟩","Rang":"⟫","rangd":"⦒","range":"⦥","rangle":"⟩","raquo":"»","rarrap":"⥵","rarrb":"⇥","rarrbfs":"⤠","rarrc":"⤳","rarr":"→","Rarr":"↠","rArr":"⇒","rarrfs":"⤞","rarrhk":"↪","rarrlp":"↬","rarrpl":"⥅","rarrsim":"⥴","Rarrtl":"⤖","rarrtl":"↣","rarrw":"↝","ratail":"⤚","rAtail":"⤜","ratio":"∶","rationals":"ℚ","rbarr":"⤍","rBarr":"⤏","RBarr":"⤐","rbbrk":"❳","rbrace":"}","rbrack":"]","rbrke":"⦌","rbrksld":"⦎","rbrkslu":"⦐","Rcaron":"Ř","rcaron":"ř","Rcedil":"Ŗ","rcedil":"ŗ","rceil":"⌉","rcub":"}","Rcy":"Р","rcy":"р","rdca":"⤷","rdldhar":"⥩","rdquo":"”","rdquor":"”","rdsh":"↳","real":"ℜ","realine":"ℛ","realpart":"ℜ","reals":"ℝ","Re":"ℜ","rect":"▭","reg":"®","REG":"®","ReverseElement":"∋","ReverseEquilibrium":"⇋","ReverseUpEquilibrium":"⥯","rfisht":"⥽","rfloor":"⌋","rfr":"𝔯","Rfr":"ℜ","rHar":"⥤","rhard":"⇁","rharu":"⇀","rharul":"⥬","Rho":"Ρ","rho":"ρ","rhov":"ϱ","RightAngleBracket":"⟩","RightArrowBar":"⇥","rightarrow":"→","RightArrow":"→","Rightarrow":"⇒","RightArrowLeftArrow":"⇄","rightarrowtail":"↣","RightCeiling":"⌉","RightDoubleBracket":"⟧","RightDownTeeVector":"⥝","RightDownVectorBar":"⥕","RightDownVector":"⇂","RightFloor":"⌋","rightharpoondown":"⇁","rightharpoonup":"⇀","rightleftarrows":"⇄","rightleftharpoons":"⇌","rightrightarrows":"⇉","rightsquigarrow":"↝","RightTeeArrow":"↦","RightTee":"⊢","RightTeeVector":"⥛","rightthreetimes":"⋌","RightTriangleBar":"⧐","RightTriangle":"⊳","RightTriangleEqual":"⊵","RightUpDownVector":"⥏","RightUpTeeVector":"⥜","RightUpVectorBar":"⥔","RightUpVector":"↾","RightVectorBar":"⥓","RightVector":"⇀","ring":"˚","risingdotseq":"≓","rlarr":"⇄","rlhar":"⇌","rlm":"‏","rmoustache":"⎱","rmoust":"⎱","rnmid":"⫮","roang":"⟭","roarr":"⇾","robrk":"⟧","ropar":"⦆","ropf":"𝕣","Ropf":"ℝ","roplus":"⨮","rotimes":"⨵","RoundImplies":"⥰","rpar":")","rpargt":"⦔","rppolint":"⨒","rrarr":"⇉","Rrightarrow":"⇛","rsaquo":"›","rscr":"𝓇","Rscr":"ℛ","rsh":"↱","Rsh":"↱","rsqb":"]","rsquo":"’","rsquor":"’","rthree":"⋌","rtimes":"⋊","rtri":"▹","rtrie":"⊵","rtrif":"▸","rtriltri":"⧎","RuleDelayed":"⧴","ruluhar":"⥨","rx":"℞","Sacute":"Ś","sacute":"ś","sbquo":"‚","scap":"⪸","Scaron":"Š","scaron":"š","Sc":"⪼","sc":"≻","sccue":"≽","sce":"⪰","scE":"⪴","Scedil":"Ş","scedil":"ş","Scirc":"Ŝ","scirc":"ŝ","scnap":"⪺","scnE":"⪶","scnsim":"⋩","scpolint":"⨓","scsim":"≿","Scy":"С","scy":"с","sdotb":"⊡","sdot":"⋅","sdote":"⩦","searhk":"⤥","searr":"↘","seArr":"⇘","searrow":"↘","sect":"§","semi":";","seswar":"⤩","setminus":"∖","setmn":"∖","sext":"✶","Sfr":"𝔖","sfr":"𝔰","sfrown":"⌢","sharp":"♯","SHCHcy":"Щ","shchcy":"щ","SHcy":"Ш","shcy":"ш","ShortDownArrow":"↓","ShortLeftArrow":"←","shortmid":"∣","shortparallel":"∥","ShortRightArrow":"→","ShortUpArrow":"↑","shy":"­","Sigma":"Σ","sigma":"σ","sigmaf":"ς","sigmav":"ς","sim":"∼","simdot":"⩪","sime":"≃","simeq":"≃","simg":"⪞","simgE":"⪠","siml":"⪝","simlE":"⪟","simne":"≆","simplus":"⨤","simrarr":"⥲","slarr":"←","SmallCircle":"∘","smallsetminus":"∖","smashp":"⨳","smeparsl":"⧤","smid":"∣","smile":"⌣","smt":"⪪","smte":"⪬","smtes":"⪬︀","SOFTcy":"Ь","softcy":"ь","solbar":"⌿","solb":"⧄","sol":"/","Sopf":"𝕊","sopf":"𝕤","spades":"♠","spadesuit":"♠","spar":"∥","sqcap":"⊓","sqcaps":"⊓︀","sqcup":"⊔","sqcups":"⊔︀","Sqrt":"√","sqsub":"⊏","sqsube":"⊑","sqsubset":"⊏","sqsubseteq":"⊑","sqsup":"⊐","sqsupe":"⊒","sqsupset":"⊐","sqsupseteq":"⊒","square":"□","Square":"□","SquareIntersection":"⊓","SquareSubset":"⊏","SquareSubsetEqual":"⊑","SquareSuperset":"⊐","SquareSupersetEqual":"⊒","SquareUnion":"⊔","squarf":"▪","squ":"□","squf":"▪","srarr":"→","Sscr":"𝒮","sscr":"𝓈","ssetmn":"∖","ssmile":"⌣","sstarf":"⋆","Star":"⋆","star":"☆","starf":"★","straightepsilon":"ϵ","straightphi":"ϕ","strns":"¯","sub":"⊂","Sub":"⋐","subdot":"⪽","subE":"⫅","sube":"⊆","subedot":"⫃","submult":"⫁","subnE":"⫋","subne":"⊊","subplus":"⪿","subrarr":"⥹","subset":"⊂","Subset":"⋐","subseteq":"⊆","subseteqq":"⫅","SubsetEqual":"⊆","subsetneq":"⊊","subsetneqq":"⫋","subsim":"⫇","subsub":"⫕","subsup":"⫓","succapprox":"⪸","succ":"≻","succcurlyeq":"≽","Succeeds":"≻","SucceedsEqual":"⪰","SucceedsSlantEqual":"≽","SucceedsTilde":"≿","succeq":"⪰","succnapprox":"⪺","succneqq":"⪶","succnsim":"⋩","succsim":"≿","SuchThat":"∋","sum":"∑","Sum":"∑","sung":"♪","sup1":"¹","sup2":"²","sup3":"³","sup":"⊃","Sup":"⋑","supdot":"⪾","supdsub":"⫘","supE":"⫆","supe":"⊇","supedot":"⫄","Superset":"⊃","SupersetEqual":"⊇","suphsol":"⟉","suphsub":"⫗","suplarr":"⥻","supmult":"⫂","supnE":"⫌","supne":"⊋","supplus":"⫀","supset":"⊃","Supset":"⋑","supseteq":"⊇","supseteqq":"⫆","supsetneq":"⊋","supsetneqq":"⫌","supsim":"⫈","supsub":"⫔","supsup":"⫖","swarhk":"⤦","swarr":"↙","swArr":"⇙","swarrow":"↙","swnwar":"⤪","szlig":"ß","Tab":"\\t","target":"⌖","Tau":"Τ","tau":"τ","tbrk":"⎴","Tcaron":"Ť","tcaron":"ť","Tcedil":"Ţ","tcedil":"ţ","Tcy":"Т","tcy":"т","tdot":"⃛","telrec":"⌕","Tfr":"𝔗","tfr":"𝔱","there4":"∴","therefore":"∴","Therefore":"∴","Theta":"Θ","theta":"θ","thetasym":"ϑ","thetav":"ϑ","thickapprox":"≈","thicksim":"∼","ThickSpace":"  ","ThinSpace":" ","thinsp":" ","thkap":"≈","thksim":"∼","THORN":"Þ","thorn":"þ","tilde":"˜","Tilde":"∼","TildeEqual":"≃","TildeFullEqual":"≅","TildeTilde":"≈","timesbar":"⨱","timesb":"⊠","times":"×","timesd":"⨰","tint":"∭","toea":"⤨","topbot":"⌶","topcir":"⫱","top":"⊤","Topf":"𝕋","topf":"𝕥","topfork":"⫚","tosa":"⤩","tprime":"‴","trade":"™","TRADE":"™","triangle":"▵","triangledown":"▿","triangleleft":"◃","trianglelefteq":"⊴","triangleq":"≜","triangleright":"▹","trianglerighteq":"⊵","tridot":"◬","trie":"≜","triminus":"⨺","TripleDot":"⃛","triplus":"⨹","trisb":"⧍","tritime":"⨻","trpezium":"⏢","Tscr":"𝒯","tscr":"𝓉","TScy":"Ц","tscy":"ц","TSHcy":"Ћ","tshcy":"ћ","Tstrok":"Ŧ","tstrok":"ŧ","twixt":"≬","twoheadleftarrow":"↞","twoheadrightarrow":"↠","Uacute":"Ú","uacute":"ú","uarr":"↑","Uarr":"↟","uArr":"⇑","Uarrocir":"⥉","Ubrcy":"Ў","ubrcy":"ў","Ubreve":"Ŭ","ubreve":"ŭ","Ucirc":"Û","ucirc":"û","Ucy":"У","ucy":"у","udarr":"⇅","Udblac":"Ű","udblac":"ű","udhar":"⥮","ufisht":"⥾","Ufr":"𝔘","ufr":"𝔲","Ugrave":"Ù","ugrave":"ù","uHar":"⥣","uharl":"↿","uharr":"↾","uhblk":"▀","ulcorn":"⌜","ulcorner":"⌜","ulcrop":"⌏","ultri":"◸","Umacr":"Ū","umacr":"ū","uml":"¨","UnderBar":"_","UnderBrace":"⏟","UnderBracket":"⎵","UnderParenthesis":"⏝","Union":"⋃","UnionPlus":"⊎","Uogon":"Ų","uogon":"ų","Uopf":"𝕌","uopf":"𝕦","UpArrowBar":"⤒","uparrow":"↑","UpArrow":"↑","Uparrow":"⇑","UpArrowDownArrow":"⇅","updownarrow":"↕","UpDownArrow":"↕","Updownarrow":"⇕","UpEquilibrium":"⥮","upharpoonleft":"↿","upharpoonright":"↾","uplus":"⊎","UpperLeftArrow":"↖","UpperRightArrow":"↗","upsi":"υ","Upsi":"ϒ","upsih":"ϒ","Upsilon":"Υ","upsilon":"υ","UpTeeArrow":"↥","UpTee":"⊥","upuparrows":"⇈","urcorn":"⌝","urcorner":"⌝","urcrop":"⌎","Uring":"Ů","uring":"ů","urtri":"◹","Uscr":"𝒰","uscr":"𝓊","utdot":"⋰","Utilde":"Ũ","utilde":"ũ","utri":"▵","utrif":"▴","uuarr":"⇈","Uuml":"Ü","uuml":"ü","uwangle":"⦧","vangrt":"⦜","varepsilon":"ϵ","varkappa":"ϰ","varnothing":"∅","varphi":"ϕ","varpi":"ϖ","varpropto":"∝","varr":"↕","vArr":"⇕","varrho":"ϱ","varsigma":"ς","varsubsetneq":"⊊︀","varsubsetneqq":"⫋︀","varsupsetneq":"⊋︀","varsupsetneqq":"⫌︀","vartheta":"ϑ","vartriangleleft":"⊲","vartriangleright":"⊳","vBar":"⫨","Vbar":"⫫","vBarv":"⫩","Vcy":"В","vcy":"в","vdash":"⊢","vDash":"⊨","Vdash":"⊩","VDash":"⊫","Vdashl":"⫦","veebar":"⊻","vee":"∨","Vee":"⋁","veeeq":"≚","vellip":"⋮","verbar":"|","Verbar":"‖","vert":"|","Vert":"‖","VerticalBar":"∣","VerticalLine":"|","VerticalSeparator":"❘","VerticalTilde":"≀","VeryThinSpace":" ","Vfr":"𝔙","vfr":"𝔳","vltri":"⊲","vnsub":"⊂⃒","vnsup":"⊃⃒","Vopf":"𝕍","vopf":"𝕧","vprop":"∝","vrtri":"⊳","Vscr":"𝒱","vscr":"𝓋","vsubnE":"⫋︀","vsubne":"⊊︀","vsupnE":"⫌︀","vsupne":"⊋︀","Vvdash":"⊪","vzigzag":"⦚","Wcirc":"Ŵ","wcirc":"ŵ","wedbar":"⩟","wedge":"∧","Wedge":"⋀","wedgeq":"≙","weierp":"℘","Wfr":"𝔚","wfr":"𝔴","Wopf":"𝕎","wopf":"𝕨","wp":"℘","wr":"≀","wreath":"≀","Wscr":"𝒲","wscr":"𝓌","xcap":"⋂","xcirc":"◯","xcup":"⋃","xdtri":"▽","Xfr":"𝔛","xfr":"𝔵","xharr":"⟷","xhArr":"⟺","Xi":"Ξ","xi":"ξ","xlarr":"⟵","xlArr":"⟸","xmap":"⟼","xnis":"⋻","xodot":"⨀","Xopf":"𝕏","xopf":"𝕩","xoplus":"⨁","xotime":"⨂","xrarr":"⟶","xrArr":"⟹","Xscr":"𝒳","xscr":"𝓍","xsqcup":"⨆","xuplus":"⨄","xutri":"△","xvee":"⋁","xwedge":"⋀","Yacute":"Ý","yacute":"ý","YAcy":"Я","yacy":"я","Ycirc":"Ŷ","ycirc":"ŷ","Ycy":"Ы","ycy":"ы","yen":"¥","Yfr":"𝔜","yfr":"𝔶","YIcy":"Ї","yicy":"ї","Yopf":"𝕐","yopf":"𝕪","Yscr":"𝒴","yscr":"𝓎","YUcy":"Ю","yucy":"ю","yuml":"ÿ","Yuml":"Ÿ","Zacute":"Ź","zacute":"ź","Zcaron":"Ž","zcaron":"ž","Zcy":"З","zcy":"з","Zdot":"Ż","zdot":"ż","zeetrf":"ℨ","ZeroWidthSpace":"​","Zeta":"Ζ","zeta":"ζ","zfr":"𝔷","Zfr":"ℨ","ZHcy":"Ж","zhcy":"ж","zigrarr":"⇝","zopf":"𝕫","Zopf":"ℤ","Zscr":"𝒵","zscr":"𝓏","zwj":"‍","zwnj":"‌"}')},function(e,t,r){"use strict";var n={};function s(e,t,r){var o,i,a,u,c,l="";for("string"!=typeof t&&(r=t,t=s.defaultChars),void 0===r&&(r=!0),c=function(e){var t,r,s=n[e];if(s)return s;for(s=n[e]=[],t=0;t<128;t++)r=String.fromCharCode(t),/^[0-9a-z]$/i.test(r)?s.push(r):s.push("%"+("0"+t.toString(16).toUpperCase()).slice(-2));for(t=0;t=55296&&a<=57343){if(a>=55296&&a<=56319&&o+1=56320&&u<=57343){l+=encodeURIComponent(e[o]+e[o+1]),o++;continue}l+="%EF%BF%BD"}else l+=encodeURIComponent(e[o]);return l}s.defaultChars=";/?:@&=+$,-_.!~*'()#",s.componentChars="-_.!~*'()",e.exports=s},function(e,t,r){"use strict";var n={};function s(e,t){var r;return"string"!=typeof t&&(t=s.defaultChars),r=function(e){var t,r,s=n[e];if(s)return s;for(s=n[e]=[],t=0;t<128;t++)r=String.fromCharCode(t),s.push(r);for(t=0;t=55296&&u<=57343?"���":String.fromCharCode(u),t+=6):240==(248&s)&&t+91114111?c+="����":(u-=65536,c+=String.fromCharCode(55296+(u>>10),56320+(1023&u))),t+=9):c+="�";return c}))}s.defaultChars=";/?:@&=+$,#",s.componentChars="",e.exports=s},function(e,t,r){"use strict";e.exports=function(e){var t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",e.hostname&&-1!==e.hostname.indexOf(":")?t+="["+e.hostname+"]":t+=e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",t+=e.search||"",t+=e.hash||""}},function(e,t,r){"use strict";function n(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}var s=/^([a-z0-9.+-]+:)/i,o=/:[0-9]*$/,i=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,a=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),u=["'"].concat(a),c=["%","/","?",";","#"].concat(u),l=["/","?","#"],p=/^[+a-z0-9A-Z_-]{0,63}$/,h=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,f={javascript:!0,"javascript:":!0},d={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};n.prototype.parse=function(e,t){var r,n,o,a,u,m=e;if(m=m.trim(),!t&&1===e.split("#").length){var g=i.exec(m);if(g)return this.pathname=g[1],g[2]&&(this.search=g[2]),this}var _=s.exec(m);if(_&&(o=(_=_[0]).toLowerCase(),this.protocol=_,m=m.substr(_.length)),(t||_||m.match(/^\/\/[^@\/]+@[^@\/]+/))&&(!(u="//"===m.substr(0,2))||_&&f[_]||(m=m.substr(2),this.slashes=!0)),!f[_]&&(u||_&&!d[_])){var b,k,C=-1;for(r=0;r127?x+="x":x+=y[F];if(!x.match(p)){var w=A.slice(0,r),q=A.slice(r+1),S=y.match(h);S&&(w.push(S[1]),q.unshift(S[2])),q.length&&(m=q.join(".")+m),this.hostname=w.join(".");break}}}}this.hostname.length>255&&(this.hostname=""),D&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}var L=m.indexOf("#");-1!==L&&(this.hash=m.substr(L),m=m.slice(0,L));var z=m.indexOf("?");return-1!==z&&(this.search=m.substr(z),m=m.slice(0,z)),m&&(this.pathname=m),d[o]&&this.hostname&&!this.pathname&&(this.pathname=""),this},n.prototype.parseHost=function(e){var t=o.exec(e);t&&(":"!==(t=t[0])&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)},e.exports=function(e,t){if(e&&e instanceof n)return e;var r=new n;return r.parse(e,t),r}},function(e,t,r){"use strict";t.Any=r(18),t.Cc=r(19),t.Cf=r(20),t.P=r(4),t.Z=r(21)},function(e,t){e.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/},function(e,t){e.exports=/[\0-\x1F\x7F-\x9F]/},function(e,t){e.exports=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804\uDCBD|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/},function(e,t){e.exports=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/},function(e,t,r){"use strict";t.parseLinkLabel=r(23),t.parseLinkDestination=r(24),t.parseLinkTitle=r(25)},function(e,t,r){"use strict";e.exports=function(e,t,r){var n,s,o,i,a=-1,u=e.posMax,c=e.pos;for(e.pos=t+1,n=1;e.pos32)return a;if(41===s){if(0===o)break;o--}t++}return i===t||0!==o||(a.str=n(e.slice(i,t)),a.lines=0,a.pos=t,a.ok=!0),a}},function(e,t,r){"use strict";var n=r(0).unescapeAll;e.exports=function(e,t,r){var s,o,i=0,a=t,u={ok:!1,pos:0,lines:0,str:""};if(t>=r)return u;if(34!==(o=e.charCodeAt(t))&&39!==o&&40!==o)return u;for(t++,40===o&&(o=41);t"+o(e[t].content)+""},i.code_block=function(e,t,r,n,s){var i=e[t];return""+o(e[t].content)+"\n"},i.fence=function(e,t,r,n,i){var a,u,c,l,p,h=e[t],f=h.info?s(h.info).trim():"",d="",m="";return f&&(d=(c=f.split(/(\s+)/g))[0],m=c.slice(2).join("")),0===(a=r.highlight&&r.highlight(h.content,d,m)||o(h.content)).indexOf(""+a+"\n"):"
"+a+"
\n"},i.image=function(e,t,r,n,s){var o=e[t];return o.attrs[o.attrIndex("alt")][1]=s.renderInlineAsText(o.children,r,n),s.renderToken(e,t,r)},i.hardbreak=function(e,t,r){return r.xhtmlOut?"
\n":"
\n"},i.softbreak=function(e,t,r){return r.breaks?r.xhtmlOut?"
\n":"
\n":"\n"},i.text=function(e,t){return o(e[t].content)},i.html_block=function(e,t){return e[t].content},i.html_inline=function(e,t){return e[t].content},a.prototype.renderAttrs=function(e){var t,r,n;if(!e.attrs)return"";for(n="",t=0,r=e.attrs.length;t\n":">")},a.prototype.renderInline=function(e,t,r){for(var n,s="",o=this.rules,i=0,a=e.length;i/i.test(e)}e.exports=function(e){var t,r,o,i,a,u,c,l,p,h,f,d,m,g,_,b,k,C,v=e.tokens;if(e.md.options.linkify)for(r=0,o=v.length;r=0;t--)if("link_close"!==(u=i[t]).type){if("html_inline"===u.type&&(C=u.content,/^\s]/i.test(C)&&m>0&&m--,s(u.content)&&m++),!(m>0)&&"text"===u.type&&e.md.linkify.test(u.content)){for(p=u.content,k=e.md.linkify.match(p),c=[],d=u.level,f=0,l=0;lf&&((a=new e.Token("text","",0)).content=p.slice(f,h),a.level=d,c.push(a)),(a=new e.Token("link_open","a",1)).attrs=[["href",_]],a.level=d++,a.markup="linkify",a.info="auto",c.push(a),(a=new e.Token("text","",0)).content=b,a.level=d,c.push(a),(a=new e.Token("link_close","a",-1)).level=--d,a.markup="linkify",a.info="auto",c.push(a),f=k[l].lastIndex);f=0;t--)"text"!==(r=e[t]).type||n||(r.content=r.content.replace(o,a)),"link_open"===r.type&&"auto"===r.info&&n--,"link_close"===r.type&&"auto"===r.info&&n++}function c(e){var t,r,s=0;for(t=e.length-1;t>=0;t--)"text"!==(r=e[t]).type||s||n.test(r.content)&&(r.content=r.content.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---(?=[^-]|$)/gm,"$1—").replace(/(^|\s)--(?=\s|$)/gm,"$1–").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm,"$1–")),"link_open"===r.type&&"auto"===r.info&&s--,"link_close"===r.type&&"auto"===r.info&&s++}e.exports=function(e){var t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&(s.test(e.tokens[t].content)&&u(e.tokens[t].children),n.test(e.tokens[t].content)&&c(e.tokens[t].children))}},function(e,t,r){"use strict";var n=r(0).isWhiteSpace,s=r(0).isPunctChar,o=r(0).isMdAsciiPunct,i=/['"]/,a=/['"]/g;function u(e,t,r){return e.substr(0,t)+r+e.substr(t+1)}function c(e,t){var r,i,c,l,p,h,f,d,m,g,_,b,k,C,v,D,A,y,x,F,E;for(x=[],r=0;r=0&&!(x[A].level<=f);A--);if(x.length=A+1,"text"===i.type){p=0,h=(c=i.content).length;e:for(;p=0)m=c.charCodeAt(l.index-1);else for(A=r-1;A>=0&&("softbreak"!==e[A].type&&"hardbreak"!==e[A].type);A--)if(e[A].content){m=e[A].content.charCodeAt(e[A].content.length-1);break}if(g=32,p=48&&m<=57&&(D=v=!1),v&&D&&(v=_,D=b),v||D){if(D)for(A=x.length-1;A>=0&&(d=x[A],!(x[A].level=0;t--)"inline"===e.tokens[t].type&&i.test(e.tokens[t].content)&&c(e.tokens[t].children,e)}},function(e,t,r){"use strict";var n=r(2);function s(e,t,r){this.src=e,this.env=r,this.tokens=[],this.inlineMode=!1,this.md=t}s.prototype.Token=n,e.exports=s},function(e,t,r){"use strict";var n=r(1),s=[["table",r(36),["paragraph","reference"]],["code",r(37)],["fence",r(38),["paragraph","reference","blockquote","list"]],["blockquote",r(39),["paragraph","reference","blockquote","list"]],["hr",r(40),["paragraph","reference","blockquote","list"]],["list",r(41),["paragraph","reference","blockquote"]],["reference",r(42)],["heading",r(43),["paragraph","reference","blockquote"]],["lheading",r(44)],["html_block",r(45),["paragraph","reference","blockquote"]],["paragraph",r(47)]];function o(){this.ruler=new n;for(var e=0;e=r))&&!(e.sCount[i]=u){e.line=r;break}for(n=0;nr)return!1;if(h=t+1,e.sCount[h]=4)return!1;if((c=e.bMarks[h]+e.tShift[h])>=e.eMarks[h])return!1;if(124!==(a=e.src.charCodeAt(c++))&&45!==a&&58!==a)return!1;for(;c=4)return!1;if((f=o(u)).length&&""===f[0]&&f.shift(),f.length&&""===f[f.length-1]&&f.pop(),0===(d=f.length)||d!==g.length)return!1;if(i)return!0;for(C=e.parentType,e.parentType="table",D=e.md.block.ruler.getRules("blockquote"),(m=e.push("table_open","table",1)).map=b=[t,0],(m=e.push("thead_open","thead",1)).map=[t,t+1],(m=e.push("tr_open","tr",1)).map=[t,t+1],l=0;l=4)break;for((f=o(u)).length&&""===f[0]&&f.shift(),f.length&&""===f[f.length-1]&&f.pop(),h===t+2&&((m=e.push("tbody_open","tbody",1)).map=k=[t+2,0]),(m=e.push("tr_open","tr",1)).map=[h,h+1],l=0;l=4))break;s=++n}return e.line=s,(o=e.push("code_block","code",0)).content=e.getLines(t,s,4+e.blkIndent,!0),o.map=[t,e.line],!0}},function(e,t,r){"use strict";e.exports=function(e,t,r,n){var s,o,i,a,u,c,l,p=!1,h=e.bMarks[t]+e.tShift[t],f=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(h+3>f)return!1;if(126!==(s=e.src.charCodeAt(h))&&96!==s)return!1;if(u=h,(o=(h=e.skipChars(h,s))-u)<3)return!1;if(l=e.src.slice(u,h),i=e.src.slice(h,f),96===s&&i.indexOf(String.fromCharCode(s))>=0)return!1;if(n)return!0;for(a=t;!(++a>=r)&&!((h=u=e.bMarks[a]+e.tShift[a])<(f=e.eMarks[a])&&e.sCount[a]=4||(h=e.skipChars(h,s))-u=4)return!1;if(62!==e.src.charCodeAt(F++))return!1;if(s)return!0;for(u=f=e.sCount[t]+1,32===e.src.charCodeAt(F)?(F++,u++,f++,o=!1,C=!0):9===e.src.charCodeAt(F)?(C=!0,(e.bsCount[t]+f)%4==3?(F++,u++,f++,o=!1):o=!0):C=!1,d=[e.bMarks[t]],e.bMarks[t]=F;F=E,b=[e.sCount[t]],e.sCount[t]=f-u,k=[e.tShift[t]],e.tShift[t]=F-e.bMarks[t],D=e.md.block.ruler.getRules("blockquote"),_=e.parentType,e.parentType="blockquote",h=t+1;h=(E=e.eMarks[h])));h++)if(62!==e.src.charCodeAt(F++)||y){if(l)break;for(v=!1,a=0,c=D.length;a=E,m.push(e.bsCount[h]),e.bsCount[h]=e.sCount[h]+1+(C?1:0),b.push(e.sCount[h]),e.sCount[h]=f-u,k.push(e.tShift[h]),e.tShift[h]=F-e.bMarks[h]}for(g=e.blkIndent,e.blkIndent=0,(A=e.push("blockquote_open","blockquote",1)).markup=">",A.map=p=[t,0],e.md.block.tokenize(e,t,h),(A=e.push("blockquote_close","blockquote",-1)).markup=">",e.lineMax=x,e.parentType=_,p[1]=e.line,a=0;a=4)return!1;if(42!==(o=e.src.charCodeAt(c++))&&45!==o&&95!==o)return!1;for(i=1;c=i)return-1;if((r=e.src.charCodeAt(o++))<48||r>57)return-1;for(;;){if(o>=i)return-1;if(!((r=e.src.charCodeAt(o++))>=48&&r<=57)){if(41===r||46===r)break;return-1}if(o-s>=10)return-1}return o=4)return!1;if(e.listIndent>=0&&e.sCount[t]-e.listIndent>=4&&e.sCount[t]=e.blkIndent&&(B=!0),(w=o(e,t))>=0){if(h=!0,S=e.bMarks[t]+e.tShift[t],b=Number(e.src.substr(S,w-S-1)),B&&1!==b)return!1}else{if(!((w=s(e,t))>=0))return!1;h=!1}if(B&&e.skipSpaces(w)>=e.eMarks[t])return!1;if(_=e.src.charCodeAt(w-1),n)return!0;for(g=e.tokens.length,h?(T=e.push("ordered_list_open","ol",1),1!==b&&(T.attrs=[["start",b]])):T=e.push("bullet_list_open","ul",1),T.map=m=[t,0],T.markup=String.fromCharCode(_),C=t,q=!1,z=e.md.block.ruler.getRules("list"),A=e.parentType,e.parentType="list";C=k?1:v-p)>4&&(l=1),c=p+l,(T=e.push("list_item_open","li",1)).markup=String.fromCharCode(_),T.map=f=[t,0],F=e.tight,x=e.tShift[t],y=e.sCount[t],D=e.listIndent,e.listIndent=e.blkIndent,e.blkIndent=c,e.tight=!0,e.tShift[t]=a-e.bMarks[t],e.sCount[t]=v,a>=k&&e.isEmpty(t+1)?e.line=Math.min(e.line+2,r):e.md.block.tokenize(e,t,r,!0),e.tight&&!q||(I=!1),q=e.line-t>1&&e.isEmpty(e.line-1),e.blkIndent=e.listIndent,e.listIndent=D,e.tShift[t]=x,e.sCount[t]=y,e.tight=F,(T=e.push("list_item_close","li",-1)).markup=String.fromCharCode(_),C=t=e.line,f[1]=C,a=e.bMarks[t],C>=r)break;if(e.sCount[C]=4)break;for(L=!1,u=0,d=z.length;u=4)return!1;if(91!==e.src.charCodeAt(A))return!1;for(;++A3||e.sCount[x]<0)){for(k=!1,p=0,h=C.length;p=4)return!1;if(35!==(o=e.src.charCodeAt(c))||c>=l)return!1;for(i=1,o=e.src.charCodeAt(++c);35===o&&c6||cc&&n(e.src.charCodeAt(a-1))&&(l=a),e.line=t+1,(u=e.push("heading_open","h"+String(i),1)).markup="########".slice(0,i),u.map=[t,e.line],(u=e.push("inline","",0)).content=e.src.slice(c,l).trim(),u.map=[t,e.line],u.children=[],(u=e.push("heading_close","h"+String(i),-1)).markup="########".slice(0,i)),!0)}},function(e,t,r){"use strict";e.exports=function(e,t,r){var n,s,o,i,a,u,c,l,p,h,f=t+1,d=e.md.block.ruler.getRules("paragraph");if(e.sCount[t]-e.blkIndent>=4)return!1;for(h=e.parentType,e.parentType="paragraph";f3)){if(e.sCount[f]>=e.blkIndent&&(u=e.bMarks[f]+e.tShift[f])<(c=e.eMarks[f])&&(45===(p=e.src.charCodeAt(u))||61===p)&&(u=e.skipChars(u,p),(u=e.skipSpaces(u))>=c)){l=61===p?1:2;break}if(!(e.sCount[f]<0)){for(s=!1,o=0,i=d.length;o|$))/i,/<\/(script|pre|style)>/i,!0],[/^/,!0],[/^<\?/,/\?>/,!0],[/^/,!0],[/^/,!0],[new RegExp("^|$))","i"),/^$/,!0],[new RegExp(s.source+"\\s*$"),/^$/,!1]];e.exports=function(e,t,r,n){var s,i,a,u,c=e.bMarks[t]+e.tShift[t],l=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(c))return!1;for(u=e.src.slice(c,l),s=0;s3||e.sCount[u]<0)){for(n=!1,s=0,o=c.length;s0&&this.level++,this.tokens.push(s),s},o.prototype.isEmpty=function(e){return this.bMarks[e]+this.tShift[e]>=this.eMarks[e]},o.prototype.skipEmptyLines=function(e){for(var t=this.lineMax;et;)if(!s(this.src.charCodeAt(--e)))return e+1;return e},o.prototype.skipChars=function(e,t){for(var r=this.src.length;er;)if(t!==this.src.charCodeAt(--e))return e+1;return e},o.prototype.getLines=function(e,t,r,n){var o,i,a,u,c,l,p,h=e;if(e>=t)return"";for(l=new Array(t-e),o=0;hr?new Array(i-r+1).join(" ")+this.src.slice(u,c):this.src.slice(u,c)}return l.join("")},o.prototype.Token=n,e.exports=o},function(e,t,r){"use strict";var n=r(1),s=[["text",r(50)],["newline",r(51)],["escape",r(52)],["backticks",r(53)],["strikethrough",r(7).tokenize],["emphasis",r(8).tokenize],["link",r(54)],["image",r(55)],["autolink",r(56)],["html_inline",r(57)],["entity",r(58)]],o=[["balance_pairs",r(59)],["strikethrough",r(7).postProcess],["emphasis",r(8).postProcess],["text_collapse",r(60)]];function i(){var e;for(this.ruler=new n,e=0;e=o)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},i.prototype.parse=function(e,t,r,n){var s,o,i,a=new this.State(e,t,r,n);for(this.tokenize(a),i=(o=this.ruler2.getRules("")).length,s=0;s=0&&32===e.pending.charCodeAt(r)?r>=1&&32===e.pending.charCodeAt(r-1)?(e.pending=e.pending.replace(/ +$/,""),e.push("hardbreak","br",0)):(e.pending=e.pending.slice(0,-1),e.push("softbreak","br",0)):e.push("softbreak","br",0)),o++;o?@[]^_`{|}~-".split("").forEach((function(e){s[e.charCodeAt(0)]=1})),e.exports=function(e,t){var r,o=e.pos,i=e.posMax;if(92!==e.src.charCodeAt(o))return!1;if(++o=m)return!1;if(g=c,(l=e.md.helpers.parseLinkDestination(e.src,c,e.posMax)).ok){for(h=e.md.normalizeLink(l.str),e.md.validateLink(h)?c=l.pos:h="",g=c;c=m||41!==e.src.charCodeAt(c))&&(_=!0),c++}if(_){if(void 0===e.env.references)return!1;if(c=0?i=e.src.slice(g,c++):c=a+1):c=a+1,i||(i=e.src.slice(u,a)),!(p=e.env.references[n(i)]))return e.pos=d,!1;h=p.href,f=p.title}return t||(e.pos=u,e.posMax=a,e.push("link_open","a",1).attrs=r=[["href",h]],f&&r.push(["title",f]),e.md.inline.tokenize(e),e.push("link_close","a",-1)),e.pos=c,e.posMax=m,!0}},function(e,t,r){"use strict";var n=r(0).normalizeReference,s=r(0).isSpace;e.exports=function(e,t){var r,o,i,a,u,c,l,p,h,f,d,m,g,_="",b=e.pos,k=e.posMax;if(33!==e.src.charCodeAt(e.pos))return!1;if(91!==e.src.charCodeAt(e.pos+1))return!1;if(c=e.pos+2,(u=e.md.helpers.parseLinkLabel(e,e.pos+1,!1))<0)return!1;if((l=u+1)=k)return!1;for(g=l,(h=e.md.helpers.parseLinkDestination(e.src,l,e.posMax)).ok&&(_=e.md.normalizeLink(h.str),e.md.validateLink(_)?l=h.pos:_=""),g=l;l=k||41!==e.src.charCodeAt(l))return e.pos=b,!1;l++}else{if(void 0===e.env.references)return!1;if(l=0?a=e.src.slice(g,l++):l=u+1):l=u+1,a||(a=e.src.slice(c,u)),!(p=e.env.references[n(a)]))return e.pos=b,!1;_=p.href,f=p.title}return t||(i=e.src.slice(c,u),e.md.inline.parse(i,e.md,e.env,m=[]),(d=e.push("image","img",0)).attrs=r=[["src",_],["alt",""]],d.children=m,d.content=i,f&&r.push(["title",f])),e.pos=l,e.posMax=k,!0}},function(e,t,r){"use strict";var n=/^([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/,s=/^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$/;e.exports=function(e,t){var r,o,i,a,u,c,l=e.pos;if(60!==e.src.charCodeAt(l))return!1;for(u=e.pos,c=e.posMax;;){if(++l>=c)return!1;if(60===(a=e.src.charCodeAt(l)))return!1;if(62===a)break}return r=e.src.slice(u+1,l),s.test(r)?(o=e.md.normalizeLink(r),!!e.md.validateLink(o)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",o]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(r),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=r.length+2,!0)):!!n.test(r)&&(o=e.md.normalizeLink("mailto:"+r),!!e.md.validateLink(o)&&(t||((i=e.push("link_open","a",1)).attrs=[["href",o]],i.markup="autolink",i.info="auto",(i=e.push("text","",0)).content=e.md.normalizeLinkText(r),(i=e.push("link_close","a",-1)).markup="autolink",i.info="auto"),e.pos+=r.length+2,!0))}},function(e,t,r){"use strict";var n=r(6).HTML_TAG_RE;e.exports=function(e,t){var r,s,o,i=e.pos;return!!e.md.options.html&&(o=e.posMax,!(60!==e.src.charCodeAt(i)||i+2>=o)&&(!(33!==(r=e.src.charCodeAt(i+1))&&63!==r&&47!==r&&!function(e){var t=32|e;return t>=97&&t<=122}(r))&&(!!(s=e.src.slice(i).match(n))&&(t||(e.push("html_inline","",0).content=e.src.slice(i,i+s[0].length)),e.pos+=s[0].length,!0))))}},function(e,t,r){"use strict";var n=r(3),s=r(0).has,o=r(0).isValidEntityCode,i=r(0).fromCodePoint,a=/^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i,u=/^&([a-z][a-z0-9]{1,31});/i;e.exports=function(e,t){var r,c,l=e.pos,p=e.posMax;if(38!==e.src.charCodeAt(l))return!1;if(l+1i;n-=o.jump+1)if((o=t[n]).marker===s.marker&&o.open&&o.end<0&&(u=!1,(o.close||s.open)&&(o.length+s.length)%3==0&&(o.length%3==0&&s.length%3==0||(u=!0)),!u)){c=n>0&&!t[n-1].open?t[n-1].jump+1:0,s.jump=r-n+c,s.open=!1,o.end=r,o.jump=c,o.close=!1,a=-1;break}-1!==a&&(l[s.marker][(s.length||0)%3]=a)}}e.exports=function(e){var t,r=e.tokens_meta,s=e.tokens_meta.length;for(n(0,e.delimiters),t=0;t0&&n++,"text"===s[t].type&&t+10&&(this.level++,this._prev_delimiters.push(this.delimiters),this.delimiters=[],o={delimiters:this.delimiters}),this.pendingLevel=this.level,this.tokens.push(s),this.tokens_meta.push(o),s},a.prototype.scanDelims=function(e,t){var r,n,a,u,c,l,p,h,f,d=e,m=!0,g=!0,_=this.posMax,b=this.src.charCodeAt(e);for(r=e>0?this.src.charCodeAt(e-1):32;d<_&&this.src.charCodeAt(d)===b;)d++;return a=d-e,n=d<_?this.src.charCodeAt(d):32,p=i(r)||o(String.fromCharCode(r)),f=i(n)||o(String.fromCharCode(n)),l=s(r),(h=s(n))?m=!1:f&&(l||p||(m=!1)),l?g=!1:p&&(h||f||(g=!1)),t?(u=m,c=g):(u=m&&(!g||p),c=g&&(!m||f)),{can_open:u,can_close:c,length:a}},a.prototype.Token=n,e.exports=a},function(e,t,r){"use strict";function n(e){var t=Array.prototype.slice.call(arguments,1);return t.forEach((function(t){t&&Object.keys(t).forEach((function(r){e[r]=t[r]}))})),e}function s(e){return Object.prototype.toString.call(e)}function o(e){return"[object Function]"===s(e)}function i(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}var a={fuzzyLink:!0,fuzzyEmail:!0,fuzzyIP:!1};var u={"http:":{validate:function(e,t,r){var n=e.slice(t);return r.re.http||(r.re.http=new RegExp("^\\/\\/"+r.re.src_auth+r.re.src_host_port_strict+r.re.src_path,"i")),r.re.http.test(n)?n.match(r.re.http)[0].length:0}},"https:":"http:","ftp:":"http:","//":{validate:function(e,t,r){var n=e.slice(t);return r.re.no_http||(r.re.no_http=new RegExp("^"+r.re.src_auth+"(?:localhost|(?:(?:"+r.re.src_domain+")\\.)+"+r.re.src_domain_root+")"+r.re.src_port+r.re.src_host_terminator+r.re.src_path,"i")),r.re.no_http.test(n)?t>=3&&":"===e[t-3]||t>=3&&"/"===e[t-3]?0:n.match(r.re.no_http)[0].length:0}},"mailto:":{validate:function(e,t,r){var n=e.slice(t);return r.re.mailto||(r.re.mailto=new RegExp("^"+r.re.src_email_name+"@"+r.re.src_host_strict,"i")),r.re.mailto.test(n)?n.match(r.re.mailto)[0].length:0}}},c="biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split("|");function l(e){var t=e.re=r(63)(e.__opts__),n=e.__tlds__.slice();function a(e){return e.replace("%TLDS%",t.src_tlds)}e.onCompile(),e.__tlds_replaced__||n.push("a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]"),n.push(t.src_xn),t.src_tlds=n.join("|"),t.email_fuzzy=RegExp(a(t.tpl_email_fuzzy),"i"),t.link_fuzzy=RegExp(a(t.tpl_link_fuzzy),"i"),t.link_no_ip_fuzzy=RegExp(a(t.tpl_link_no_ip_fuzzy),"i"),t.host_fuzzy_test=RegExp(a(t.tpl_host_fuzzy_test),"i");var u=[];function c(e,t){throw new Error('(LinkifyIt) Invalid schema "'+e+'": '+t)}e.__compiled__={},Object.keys(e.__schemas__).forEach((function(t){var r=e.__schemas__[t];if(null!==r){var n={validate:null,link:null};if(e.__compiled__[t]=n,"[object Object]"===s(r))return!function(e){return"[object RegExp]"===s(e)}(r.validate)?o(r.validate)?n.validate=r.validate:c(t,r):n.validate=function(e){return function(t,r){var n=t.slice(r);return e.test(n)?n.match(e)[0].length:0}}(r.validate),void(o(r.normalize)?n.normalize=r.normalize:r.normalize?c(t,r):n.normalize=function(e,t){t.normalize(e)});!function(e){return"[object String]"===s(e)}(r)?c(t,r):u.push(t)}})),u.forEach((function(t){e.__compiled__[e.__schemas__[t]]&&(e.__compiled__[t].validate=e.__compiled__[e.__schemas__[t]].validate,e.__compiled__[t].normalize=e.__compiled__[e.__schemas__[t]].normalize)})),e.__compiled__[""]={validate:null,normalize:function(e,t){t.normalize(e)}};var l=Object.keys(e.__compiled__).filter((function(t){return t.length>0&&e.__compiled__[t]})).map(i).join("|");e.re.schema_test=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+l+")","i"),e.re.schema_search=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+l+")","ig"),e.re.pretest=RegExp("("+e.re.schema_test.source+")|("+e.re.host_fuzzy_test.source+")|@","i"),function(e){e.__index__=-1,e.__text_cache__=""}(e)}function p(e,t){var r=e.__index__,n=e.__last_index__,s=e.__text_cache__.slice(r,n);this.schema=e.__schema__.toLowerCase(),this.index=r+t,this.lastIndex=n+t,this.raw=s,this.text=s,this.url=s}function h(e,t){var r=new p(e,t);return e.__compiled__[r.schema].normalize(r,e),r}function f(e,t){if(!(this instanceof f))return new f(e,t);var r;t||(r=e,Object.keys(r||{}).reduce((function(e,t){return e||a.hasOwnProperty(t)}),!1)&&(t=e,e={})),this.__opts__=n({},a,t),this.__index__=-1,this.__last_index__=-1,this.__schema__="",this.__text_cache__="",this.__schemas__=n({},u,e),this.__compiled__={},this.__tlds__=c,this.__tlds_replaced__=!1,this.re={},l(this)}f.prototype.add=function(e,t){return this.__schemas__[e]=t,l(this),this},f.prototype.set=function(e){return this.__opts__=n(this.__opts__,e),this},f.prototype.test=function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return!1;var t,r,n,s,o,i,a,u;if(this.re.schema_test.test(e))for((a=this.re.schema_search).lastIndex=0;null!==(t=a.exec(e));)if(s=this.testSchemaAt(e,t[2],a.lastIndex)){this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+s;break}return this.__opts__.fuzzyLink&&this.__compiled__["http:"]&&(u=e.search(this.re.host_fuzzy_test))>=0&&(this.__index__<0||u=0&&null!==(n=e.match(this.re.email_fuzzy))&&(o=n.index+n[1].length,i=n.index+n[0].length,(this.__index__<0||othis.__last_index__)&&(this.__schema__="mailto:",this.__index__=o,this.__last_index__=i)),this.__index__>=0},f.prototype.pretest=function(e){return this.re.pretest.test(e)},f.prototype.testSchemaAt=function(e,t,r){return this.__compiled__[t.toLowerCase()]?this.__compiled__[t.toLowerCase()].validate(e,r,this):0},f.prototype.match=function(e){var t=0,r=[];this.__index__>=0&&this.__text_cache__===e&&(r.push(h(this,t)),t=this.__last_index__);for(var n=t?e.slice(t):e;this.test(n);)r.push(h(this,t)),n=n.slice(this.__last_index__),t+=this.__last_index__;return r.length?r:null},f.prototype.tlds=function(e,t){return e=Array.isArray(e)?e:[e],t?(this.__tlds__=this.__tlds__.concat(e).sort().filter((function(e,t,r){return e!==r[t-1]})).reverse(),l(this),this):(this.__tlds__=e.slice(),this.__tlds_replaced__=!0,l(this),this)},f.prototype.normalize=function(e){e.schema||(e.url="http://"+e.url),"mailto:"!==e.schema||/^mailto:/i.test(e.url)||(e.url="mailto:"+e.url)},f.prototype.onCompile=function(){},e.exports=f},function(e,t,r){"use strict";e.exports=function(e){var t={};t.src_Any=r(64).source,t.src_Cc=r(65).source,t.src_Z=r(66).source,t.src_P=r(67).source,t.src_ZPCc=[t.src_Z,t.src_P,t.src_Cc].join("|"),t.src_ZCc=[t.src_Z,t.src_Cc].join("|");return t.src_pseudo_letter="(?:(?![><|]|"+t.src_ZPCc+")"+t.src_Any+")",t.src_ip4="(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",t.src_auth="(?:(?:(?!"+t.src_ZCc+"|[@/\\[\\]()]).)+@)?",t.src_port="(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?",t.src_host_terminator="(?=$|[><|]|"+t.src_ZPCc+")(?!-|_|:\\d|\\.-|\\.(?!$|"+t.src_ZPCc+"))",t.src_path="(?:[/?#](?:(?!"+t.src_ZCc+"|[><|]|[()[\\]{}.,\"'?!\\-]).|\\[(?:(?!"+t.src_ZCc+"|\\]).)*\\]|\\((?:(?!"+t.src_ZCc+"|[)]).)*\\)|\\{(?:(?!"+t.src_ZCc+'|[}]).)*\\}|\\"(?:(?!'+t.src_ZCc+'|["]).)+\\"|\\\'(?:(?!'+t.src_ZCc+"|[']).)+\\'|\\'(?="+t.src_pseudo_letter+"|[-]).|\\.{2,}[a-zA-Z0-9%/&]|\\.(?!"+t.src_ZCc+"|[.]).|"+(e&&e["---"]?"\\-(?!--(?:[^-]|$))(?:-*)|":"\\-+|")+"\\,(?!"+t.src_ZCc+").|\\!+(?!"+t.src_ZCc+"|[!]).|\\?(?!"+t.src_ZCc+"|[?]).)+|\\/)?",t.src_email_name='[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*',t.src_xn="xn--[a-z0-9\\-]{1,59}",t.src_domain_root="(?:"+t.src_xn+"|"+t.src_pseudo_letter+"{1,63})",t.src_domain="(?:"+t.src_xn+"|(?:"+t.src_pseudo_letter+")|(?:"+t.src_pseudo_letter+"(?:-|"+t.src_pseudo_letter+"){0,61}"+t.src_pseudo_letter+"))",t.src_host="(?:(?:(?:(?:"+t.src_domain+")\\.)*"+t.src_domain+"))",t.tpl_host_fuzzy="(?:"+t.src_ip4+"|(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%)))",t.tpl_host_no_ip_fuzzy="(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%))",t.src_host_strict=t.src_host+t.src_host_terminator,t.tpl_host_fuzzy_strict=t.tpl_host_fuzzy+t.src_host_terminator,t.src_host_port_strict=t.src_host+t.src_port+t.src_host_terminator,t.tpl_host_port_fuzzy_strict=t.tpl_host_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_port_no_ip_fuzzy_strict=t.tpl_host_no_ip_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_fuzzy_test="localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:"+t.src_ZPCc+"|>|$))",t.tpl_email_fuzzy='(^|[><|]|"|\\(|'+t.src_ZCc+")("+t.src_email_name+"@"+t.tpl_host_fuzzy_strict+")",t.tpl_link_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_fuzzy_strict+t.src_path+")",t.tpl_link_no_ip_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_no_ip_fuzzy_strict+t.src_path+")",t}},function(e,t){e.exports=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/},function(e,t){e.exports=/[\0-\x1F\x7F-\x9F]/},function(e,t){e.exports=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/},function(e,t){e.exports=/[!-#%-\*,-/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E44\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDF3C-\uDF3E]|\uD807[\uDC41-\uDC45\uDC70\uDC71]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/},function(e,t,r){(function(e,n){var s;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(o){t&&t.nodeType,e&&e.nodeType;var i="object"==typeof n&&n;i.global!==i&&i.window!==i&&i.self;var a,u=2147483647,c=/^xn--/,l=/[^\x20-\x7E]/,p=/[\x2E\u3002\uFF0E\uFF61]/g,h={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},f=Math.floor,d=String.fromCharCode;function m(e){throw new RangeError(h[e])}function g(e,t){for(var r=e.length,n=[];r--;)n[r]=t(e[r]);return n}function _(e,t){var r=e.split("@"),n="";return r.length>1&&(n=r[0]+"@",e=r[1]),n+g((e=e.replace(p,".")).split("."),t).join(".")}function b(e){for(var t,r,n=[],s=0,o=e.length;s=55296&&t<=56319&&s65535&&(t+=d((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=d(e)})).join("")}function C(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function v(e,t,r){var n=0;for(e=r?f(e/700):e>>1,e+=f(e/t);e>455;n+=36)e=f(e/35);return f(n+36*e/(e+38))}function D(e){var t,r,n,s,o,i,a,c,l,p,h,d=[],g=e.length,_=0,b=128,C=72;for((r=e.lastIndexOf("-"))<0&&(r=0),n=0;n=128&&m("not-basic"),d.push(e.charCodeAt(n));for(s=r>0?r+1:0;s=g&&m("invalid-input"),((c=(h=e.charCodeAt(s++))-48<10?h-22:h-65<26?h-65:h-97<26?h-97:36)>=36||c>f((u-_)/i))&&m("overflow"),_+=c*i,!(c<(l=a<=C?1:a>=C+26?26:a-C));a+=36)i>f(u/(p=36-l))&&m("overflow"),i*=p;C=v(_-o,t=d.length+1,0==o),f(_/t)>u-b&&m("overflow"),b+=f(_/t),_%=t,d.splice(_++,0,b)}return k(d)}function A(e){var t,r,n,s,o,i,a,c,l,p,h,g,_,k,D,A=[];for(g=(e=b(e)).length,t=128,r=0,o=72,i=0;i=t&&hf((u-r)/(_=n+1))&&m("overflow"),r+=(a-t)*_,t=a,i=0;iu&&m("overflow"),h==t){for(c=r,l=36;!(c<(p=l<=o?1:l>=o+26?26:l-o));l+=36)D=c-p,k=36-p,A.push(d(C(p+D%k,0))),c=f(D/k);A.push(d(C(c,0))),o=v(r,_,n==s),r=0,++n}++r,++t}return A.join("")}a={version:"1.4.1",ucs2:{decode:b,encode:k},decode:D,encode:A,toASCII:function(e){return _(e,(function(e){return l.test(e)?"xn--"+A(e):e}))},toUnicode:function(e){return _(e,(function(e){return c.test(e)?D(e.slice(4).toLowerCase()):e}))}},void 0===(s=function(){return a}.call(t,r,t,e))||(e.exports=s)}()}).call(this,r(69)(e),r(70))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){"use strict";e.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:100},components:{core:{},block:{},inline:{}}}},function(e,t,r){"use strict";e.exports={options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["paragraph"]},inline:{rules:["text"],rules2:["balance_pairs","text_collapse"]}}}},function(e,t,r){"use strict";e.exports={options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline"]},block:{rules:["blockquote","code","fence","heading","hr","html_block","lheading","list","reference","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","html_inline","image","link","newline","text"],rules2:["balance_pairs","emphasis","text_collapse"]}}}}]); \ No newline at end of file diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 40b0f925446..2414055c282 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -657,7 +657,8 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { - await this.refreshToken(refreshToken, scope, sessionId); + const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId); + onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); } catch (e) { this.pollForReconnect(sessionId, refreshToken, scope); } @@ -671,21 +672,14 @@ export class AzureActiveDirectoryService { return resolve(false); } - if (attempts === 1) { - const token = this._tokens.find(token => token.sessionId === sessionId); - if (token) { - token.accessToken = undefined; - onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(token)] }); - } - } - const delayBeforeRetry = 5 * attempts * attempts; this.clearSessionTimeout(sessionId); this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { - await this.refreshToken(refreshToken, scope, sessionId); + const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId); + onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); return resolve(true); } catch (e) { return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1)); diff --git a/extensions/notebook-markdown-extensions/.vscodeignore b/extensions/notebook-markdown-extensions/.vscodeignore index 9f1e0620775..fd41868757d 100644 --- a/extensions/notebook-markdown-extensions/.vscodeignore +++ b/extensions/notebook-markdown-extensions/.vscodeignore @@ -1,12 +1,5 @@ -test/** -test-workspace/** -src/** -tsconfig.json -out/test/** -out/** -extension.webpack.config.js +notebook/** extension-browser.webpack.config.js cgmanifest.json yarn.lock -preview-src/** webpack.config.js diff --git a/extensions/notebook-markdown-extensions/notebook-out/emoji.js b/extensions/notebook-markdown-extensions/notebook-out/emoji.js deleted file mode 100644 index 23e7d31e654..00000000000 --- a/extensions/notebook-markdown-extensions/notebook-out/emoji.js +++ /dev/null @@ -1 +0,0 @@ -!function(a){var e={};function n(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return a[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=a,n.c=e,n.d=function(a,e,o){n.o(a,e)||Object.defineProperty(a,e,{enumerable:!0,get:o})},n.r=function(a){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(a,"__esModule",{value:!0})},n.t=function(a,e){if(1&e&&(a=n(a)),8&e)return a;if(4&e&&"object"==typeof a&&a&&a.__esModule)return a;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:a}),2&e&&"string"!=typeof a)for(var i in a)n.d(o,i,function(e){return a[e]}.bind(null,i));return o},n.n=function(a){var e=a&&a.__esModule?function(){return a.default}:function(){return a};return n.d(e,"a",e),e},n.o=function(a,e){return Object.prototype.hasOwnProperty.call(a,e)},n.p="",n(n.s=3)}([,,,function(a,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),function(){if("undefined"!=typeof extendMarkdownIt){const a=n(4);extendMarkdownIt(e=>{e.use(a)})}}()},function(a,e,n){"use strict";var o=n(5),i=n(6),r=n(7);a.exports=function(a,e){var n={defs:o,shortcuts:i,enabled:[]},_=a.utils.assign({},n,e||{});r(a,_)}},function(a){a.exports=JSON.parse('{"100":"💯","1234":"🔢","grinning":"😀","smiley":"😃","smile":"😄","grin":"😁","laughing":"😆","satisfied":"😆","sweat_smile":"😅","rofl":"🤣","joy":"😂","slightly_smiling_face":"🙂","upside_down_face":"🙃","wink":"😉","blush":"😊","innocent":"😇","smiling_face_with_three_hearts":"🥰","heart_eyes":"😍","star_struck":"🤩","kissing_heart":"😘","kissing":"😗","relaxed":"☺️","kissing_closed_eyes":"😚","kissing_smiling_eyes":"😙","smiling_face_with_tear":"🥲","yum":"😋","stuck_out_tongue":"😛","stuck_out_tongue_winking_eye":"😜","zany_face":"🤪","stuck_out_tongue_closed_eyes":"😝","money_mouth_face":"🤑","hugs":"🤗","hand_over_mouth":"🤭","shushing_face":"🤫","thinking":"🤔","zipper_mouth_face":"🤐","raised_eyebrow":"🤨","neutral_face":"😐","expressionless":"😑","no_mouth":"😶","smirk":"😏","unamused":"😒","roll_eyes":"🙄","grimacing":"😬","lying_face":"🤥","relieved":"😌","pensive":"😔","sleepy":"😪","drooling_face":"🤤","sleeping":"😴","mask":"😷","face_with_thermometer":"🤒","face_with_head_bandage":"🤕","nauseated_face":"🤢","vomiting_face":"🤮","sneezing_face":"🤧","hot_face":"🥵","cold_face":"🥶","woozy_face":"🥴","dizzy_face":"😵","exploding_head":"🤯","cowboy_hat_face":"🤠","partying_face":"🥳","disguised_face":"🥸","sunglasses":"😎","nerd_face":"🤓","monocle_face":"🧐","confused":"😕","worried":"😟","slightly_frowning_face":"🙁","frowning_face":"☹️","open_mouth":"😮","hushed":"😯","astonished":"😲","flushed":"😳","pleading_face":"🥺","frowning":"😦","anguished":"😧","fearful":"😨","cold_sweat":"😰","disappointed_relieved":"😥","cry":"😢","sob":"😭","scream":"😱","confounded":"😖","persevere":"😣","disappointed":"😞","sweat":"😓","weary":"😩","tired_face":"😫","yawning_face":"🥱","triumph":"😤","rage":"😡","pout":"😡","angry":"😠","cursing_face":"🤬","smiling_imp":"😈","imp":"👿","skull":"💀","skull_and_crossbones":"☠️","hankey":"💩","poop":"💩","shit":"💩","clown_face":"🤡","japanese_ogre":"👹","japanese_goblin":"👺","ghost":"👻","alien":"👽","space_invader":"👾","robot":"🤖","smiley_cat":"😺","smile_cat":"😸","joy_cat":"😹","heart_eyes_cat":"😻","smirk_cat":"😼","kissing_cat":"😽","scream_cat":"🙀","crying_cat_face":"😿","pouting_cat":"😾","see_no_evil":"🙈","hear_no_evil":"🙉","speak_no_evil":"🙊","kiss":"💋","love_letter":"💌","cupid":"💘","gift_heart":"💝","sparkling_heart":"💖","heartpulse":"💗","heartbeat":"💓","revolving_hearts":"💞","two_hearts":"💕","heart_decoration":"💟","heavy_heart_exclamation":"❣️","broken_heart":"💔","heart":"❤️","orange_heart":"🧡","yellow_heart":"💛","green_heart":"💚","blue_heart":"💙","purple_heart":"💜","brown_heart":"🤎","black_heart":"🖤","white_heart":"🤍","anger":"💢","boom":"💥","collision":"💥","dizzy":"💫","sweat_drops":"💦","dash":"💨","hole":"🕳️","bomb":"💣","speech_balloon":"💬","eye_speech_bubble":"👁️‍🗨️","left_speech_bubble":"🗨️","right_anger_bubble":"🗯️","thought_balloon":"💭","zzz":"💤","wave":"👋","raised_back_of_hand":"🤚","raised_hand_with_fingers_splayed":"🖐️","hand":"✋","raised_hand":"✋","vulcan_salute":"🖖","ok_hand":"👌","pinched_fingers":"🤌","pinching_hand":"🤏","v":"✌️","crossed_fingers":"🤞","love_you_gesture":"🤟","metal":"🤘","call_me_hand":"🤙","point_left":"👈","point_right":"👉","point_up_2":"👆","middle_finger":"🖕","fu":"🖕","point_down":"👇","point_up":"☝️","+1":"👍","thumbsup":"👍","-1":"👎","thumbsdown":"👎","fist_raised":"✊","fist":"✊","fist_oncoming":"👊","facepunch":"👊","punch":"👊","fist_left":"🤛","fist_right":"🤜","clap":"👏","raised_hands":"🙌","open_hands":"👐","palms_up_together":"🤲","handshake":"🤝","pray":"🙏","writing_hand":"✍️","nail_care":"💅","selfie":"🤳","muscle":"💪","mechanical_arm":"🦾","mechanical_leg":"🦿","leg":"🦵","foot":"🦶","ear":"👂","ear_with_hearing_aid":"🦻","nose":"👃","brain":"🧠","anatomical_heart":"🫀","lungs":"🫁","tooth":"🦷","bone":"🦴","eyes":"👀","eye":"👁️","tongue":"👅","lips":"👄","baby":"👶","child":"🧒","boy":"👦","girl":"👧","adult":"🧑","blond_haired_person":"👱","man":"👨","bearded_person":"🧔","red_haired_man":"👨‍🦰","curly_haired_man":"👨‍🦱","white_haired_man":"👨‍🦳","bald_man":"👨‍🦲","woman":"👩","red_haired_woman":"👩‍🦰","person_red_hair":"🧑‍🦰","curly_haired_woman":"👩‍🦱","person_curly_hair":"🧑‍🦱","white_haired_woman":"👩‍🦳","person_white_hair":"🧑‍🦳","bald_woman":"👩‍🦲","person_bald":"🧑‍🦲","blond_haired_woman":"👱‍♀️","blonde_woman":"👱‍♀️","blond_haired_man":"👱‍♂️","older_adult":"🧓","older_man":"👴","older_woman":"👵","frowning_person":"🙍","frowning_man":"🙍‍♂️","frowning_woman":"🙍‍♀️","pouting_face":"🙎","pouting_man":"🙎‍♂️","pouting_woman":"🙎‍♀️","no_good":"🙅","no_good_man":"🙅‍♂️","ng_man":"🙅‍♂️","no_good_woman":"🙅‍♀️","ng_woman":"🙅‍♀️","ok_person":"🙆","ok_man":"🙆‍♂️","ok_woman":"🙆‍♀️","tipping_hand_person":"💁","information_desk_person":"💁","tipping_hand_man":"💁‍♂️","sassy_man":"💁‍♂️","tipping_hand_woman":"💁‍♀️","sassy_woman":"💁‍♀️","raising_hand":"🙋","raising_hand_man":"🙋‍♂️","raising_hand_woman":"🙋‍♀️","deaf_person":"🧏","deaf_man":"🧏‍♂️","deaf_woman":"🧏‍♀️","bow":"🙇","bowing_man":"🙇‍♂️","bowing_woman":"🙇‍♀️","facepalm":"🤦","man_facepalming":"🤦‍♂️","woman_facepalming":"🤦‍♀️","shrug":"🤷","man_shrugging":"🤷‍♂️","woman_shrugging":"🤷‍♀️","health_worker":"🧑‍⚕️","man_health_worker":"👨‍⚕️","woman_health_worker":"👩‍⚕️","student":"🧑‍🎓","man_student":"👨‍🎓","woman_student":"👩‍🎓","teacher":"🧑‍🏫","man_teacher":"👨‍🏫","woman_teacher":"👩‍🏫","judge":"🧑‍⚖️","man_judge":"👨‍⚖️","woman_judge":"👩‍⚖️","farmer":"🧑‍🌾","man_farmer":"👨‍🌾","woman_farmer":"👩‍🌾","cook":"🧑‍🍳","man_cook":"👨‍🍳","woman_cook":"👩‍🍳","mechanic":"🧑‍🔧","man_mechanic":"👨‍🔧","woman_mechanic":"👩‍🔧","factory_worker":"🧑‍🏭","man_factory_worker":"👨‍🏭","woman_factory_worker":"👩‍🏭","office_worker":"🧑‍💼","man_office_worker":"👨‍💼","woman_office_worker":"👩‍💼","scientist":"🧑‍🔬","man_scientist":"👨‍🔬","woman_scientist":"👩‍🔬","technologist":"🧑‍💻","man_technologist":"👨‍💻","woman_technologist":"👩‍💻","singer":"🧑‍🎤","man_singer":"👨‍🎤","woman_singer":"👩‍🎤","artist":"🧑‍🎨","man_artist":"👨‍🎨","woman_artist":"👩‍🎨","pilot":"🧑‍✈️","man_pilot":"👨‍✈️","woman_pilot":"👩‍✈️","astronaut":"🧑‍🚀","man_astronaut":"👨‍🚀","woman_astronaut":"👩‍🚀","firefighter":"🧑‍🚒","man_firefighter":"👨‍🚒","woman_firefighter":"👩‍🚒","police_officer":"👮","cop":"👮","policeman":"👮‍♂️","policewoman":"👮‍♀️","detective":"🕵️","male_detective":"🕵️‍♂️","female_detective":"🕵️‍♀️","guard":"💂","guardsman":"💂‍♂️","guardswoman":"💂‍♀️","ninja":"🥷","construction_worker":"👷","construction_worker_man":"👷‍♂️","construction_worker_woman":"👷‍♀️","prince":"🤴","princess":"👸","person_with_turban":"👳","man_with_turban":"👳‍♂️","woman_with_turban":"👳‍♀️","man_with_gua_pi_mao":"👲","woman_with_headscarf":"🧕","person_in_tuxedo":"🤵","man_in_tuxedo":"🤵‍♂️","woman_in_tuxedo":"🤵‍♀️","person_with_veil":"👰","man_with_veil":"👰‍♂️","woman_with_veil":"👰‍♀️","bride_with_veil":"👰‍♀️","pregnant_woman":"🤰","breast_feeding":"🤱","woman_feeding_baby":"👩‍🍼","man_feeding_baby":"👨‍🍼","person_feeding_baby":"🧑‍🍼","angel":"👼","santa":"🎅","mrs_claus":"🤶","mx_claus":"🧑‍🎄","superhero":"🦸","superhero_man":"🦸‍♂️","superhero_woman":"🦸‍♀️","supervillain":"🦹","supervillain_man":"🦹‍♂️","supervillain_woman":"🦹‍♀️","mage":"🧙","mage_man":"🧙‍♂️","mage_woman":"🧙‍♀️","fairy":"🧚","fairy_man":"🧚‍♂️","fairy_woman":"🧚‍♀️","vampire":"🧛","vampire_man":"🧛‍♂️","vampire_woman":"🧛‍♀️","merperson":"🧜","merman":"🧜‍♂️","mermaid":"🧜‍♀️","elf":"🧝","elf_man":"🧝‍♂️","elf_woman":"🧝‍♀️","genie":"🧞","genie_man":"🧞‍♂️","genie_woman":"🧞‍♀️","zombie":"🧟","zombie_man":"🧟‍♂️","zombie_woman":"🧟‍♀️","massage":"💆","massage_man":"💆‍♂️","massage_woman":"💆‍♀️","haircut":"💇","haircut_man":"💇‍♂️","haircut_woman":"💇‍♀️","walking":"🚶","walking_man":"🚶‍♂️","walking_woman":"🚶‍♀️","standing_person":"🧍","standing_man":"🧍‍♂️","standing_woman":"🧍‍♀️","kneeling_person":"🧎","kneeling_man":"🧎‍♂️","kneeling_woman":"🧎‍♀️","person_with_probing_cane":"🧑‍🦯","man_with_probing_cane":"👨‍🦯","woman_with_probing_cane":"👩‍🦯","person_in_motorized_wheelchair":"🧑‍🦼","man_in_motorized_wheelchair":"👨‍🦼","woman_in_motorized_wheelchair":"👩‍🦼","person_in_manual_wheelchair":"🧑‍🦽","man_in_manual_wheelchair":"👨‍🦽","woman_in_manual_wheelchair":"👩‍🦽","runner":"🏃","running":"🏃","running_man":"🏃‍♂️","running_woman":"🏃‍♀️","woman_dancing":"💃","dancer":"💃","man_dancing":"🕺","business_suit_levitating":"🕴️","dancers":"👯","dancing_men":"👯‍♂️","dancing_women":"👯‍♀️","sauna_person":"🧖","sauna_man":"🧖‍♂️","sauna_woman":"🧖‍♀️","climbing":"🧗","climbing_man":"🧗‍♂️","climbing_woman":"🧗‍♀️","person_fencing":"🤺","horse_racing":"🏇","skier":"⛷️","snowboarder":"🏂","golfing":"🏌️","golfing_man":"🏌️‍♂️","golfing_woman":"🏌️‍♀️","surfer":"🏄","surfing_man":"🏄‍♂️","surfing_woman":"🏄‍♀️","rowboat":"🚣","rowing_man":"🚣‍♂️","rowing_woman":"🚣‍♀️","swimmer":"🏊","swimming_man":"🏊‍♂️","swimming_woman":"🏊‍♀️","bouncing_ball_person":"⛹️","bouncing_ball_man":"⛹️‍♂️","basketball_man":"⛹️‍♂️","bouncing_ball_woman":"⛹️‍♀️","basketball_woman":"⛹️‍♀️","weight_lifting":"🏋️","weight_lifting_man":"🏋️‍♂️","weight_lifting_woman":"🏋️‍♀️","bicyclist":"🚴","biking_man":"🚴‍♂️","biking_woman":"🚴‍♀️","mountain_bicyclist":"🚵","mountain_biking_man":"🚵‍♂️","mountain_biking_woman":"🚵‍♀️","cartwheeling":"🤸","man_cartwheeling":"🤸‍♂️","woman_cartwheeling":"🤸‍♀️","wrestling":"🤼","men_wrestling":"🤼‍♂️","women_wrestling":"🤼‍♀️","water_polo":"🤽","man_playing_water_polo":"🤽‍♂️","woman_playing_water_polo":"🤽‍♀️","handball_person":"🤾","man_playing_handball":"🤾‍♂️","woman_playing_handball":"🤾‍♀️","juggling_person":"🤹","man_juggling":"🤹‍♂️","woman_juggling":"🤹‍♀️","lotus_position":"🧘","lotus_position_man":"🧘‍♂️","lotus_position_woman":"🧘‍♀️","bath":"🛀","sleeping_bed":"🛌","people_holding_hands":"🧑‍🤝‍🧑","two_women_holding_hands":"👭","couple":"👫","two_men_holding_hands":"👬","couplekiss":"💏","couplekiss_man_woman":"👩‍❤️‍💋‍👨","couplekiss_man_man":"👨‍❤️‍💋‍👨","couplekiss_woman_woman":"👩‍❤️‍💋‍👩","couple_with_heart":"💑","couple_with_heart_woman_man":"👩‍❤️‍👨","couple_with_heart_man_man":"👨‍❤️‍👨","couple_with_heart_woman_woman":"👩‍❤️‍👩","family":"👪","family_man_woman_boy":"👨‍👩‍👦","family_man_woman_girl":"👨‍👩‍👧","family_man_woman_girl_boy":"👨‍👩‍👧‍👦","family_man_woman_boy_boy":"👨‍👩‍👦‍👦","family_man_woman_girl_girl":"👨‍👩‍👧‍👧","family_man_man_boy":"👨‍👨‍👦","family_man_man_girl":"👨‍👨‍👧","family_man_man_girl_boy":"👨‍👨‍👧‍👦","family_man_man_boy_boy":"👨‍👨‍👦‍👦","family_man_man_girl_girl":"👨‍👨‍👧‍👧","family_woman_woman_boy":"👩‍👩‍👦","family_woman_woman_girl":"👩‍👩‍👧","family_woman_woman_girl_boy":"👩‍👩‍👧‍👦","family_woman_woman_boy_boy":"👩‍👩‍👦‍👦","family_woman_woman_girl_girl":"👩‍👩‍👧‍👧","family_man_boy":"👨‍👦","family_man_boy_boy":"👨‍👦‍👦","family_man_girl":"👨‍👧","family_man_girl_boy":"👨‍👧‍👦","family_man_girl_girl":"👨‍👧‍👧","family_woman_boy":"👩‍👦","family_woman_boy_boy":"👩‍👦‍👦","family_woman_girl":"👩‍👧","family_woman_girl_boy":"👩‍👧‍👦","family_woman_girl_girl":"👩‍👧‍👧","speaking_head":"🗣️","bust_in_silhouette":"👤","busts_in_silhouette":"👥","people_hugging":"🫂","footprints":"👣","monkey_face":"🐵","monkey":"🐒","gorilla":"🦍","orangutan":"🦧","dog":"🐶","dog2":"🐕","guide_dog":"🦮","service_dog":"🐕‍🦺","poodle":"🐩","wolf":"🐺","fox_face":"🦊","raccoon":"🦝","cat":"🐱","cat2":"🐈","black_cat":"🐈‍⬛","lion":"🦁","tiger":"🐯","tiger2":"🐅","leopard":"🐆","horse":"🐴","racehorse":"🐎","unicorn":"🦄","zebra":"🦓","deer":"🦌","bison":"🦬","cow":"🐮","ox":"🐂","water_buffalo":"🐃","cow2":"🐄","pig":"🐷","pig2":"🐖","boar":"🐗","pig_nose":"🐽","ram":"🐏","sheep":"🐑","goat":"🐐","dromedary_camel":"🐪","camel":"🐫","llama":"🦙","giraffe":"🦒","elephant":"🐘","mammoth":"🦣","rhinoceros":"🦏","hippopotamus":"🦛","mouse":"🐭","mouse2":"🐁","rat":"🐀","hamster":"🐹","rabbit":"🐰","rabbit2":"🐇","chipmunk":"🐿️","beaver":"🦫","hedgehog":"🦔","bat":"🦇","bear":"🐻","polar_bear":"🐻‍❄️","koala":"🐨","panda_face":"🐼","sloth":"🦥","otter":"🦦","skunk":"🦨","kangaroo":"🦘","badger":"🦡","feet":"🐾","paw_prints":"🐾","turkey":"🦃","chicken":"🐔","rooster":"🐓","hatching_chick":"🐣","baby_chick":"🐤","hatched_chick":"🐥","bird":"🐦","penguin":"🐧","dove":"🕊️","eagle":"🦅","duck":"🦆","swan":"🦢","owl":"🦉","dodo":"🦤","feather":"🪶","flamingo":"🦩","peacock":"🦚","parrot":"🦜","frog":"🐸","crocodile":"🐊","turtle":"🐢","lizard":"🦎","snake":"🐍","dragon_face":"🐲","dragon":"🐉","sauropod":"🦕","t-rex":"🦖","whale":"🐳","whale2":"🐋","dolphin":"🐬","flipper":"🐬","seal":"🦭","fish":"🐟","tropical_fish":"🐠","blowfish":"🐡","shark":"🦈","octopus":"🐙","shell":"🐚","snail":"🐌","butterfly":"🦋","bug":"🐛","ant":"🐜","bee":"🐝","honeybee":"🐝","beetle":"🪲","lady_beetle":"🐞","cricket":"🦗","cockroach":"🪳","spider":"🕷️","spider_web":"🕸️","scorpion":"🦂","mosquito":"🦟","fly":"🪰","worm":"🪱","microbe":"🦠","bouquet":"💐","cherry_blossom":"🌸","white_flower":"💮","rosette":"🏵️","rose":"🌹","wilted_flower":"🥀","hibiscus":"🌺","sunflower":"🌻","blossom":"🌼","tulip":"🌷","seedling":"🌱","potted_plant":"🪴","evergreen_tree":"🌲","deciduous_tree":"🌳","palm_tree":"🌴","cactus":"🌵","ear_of_rice":"🌾","herb":"🌿","shamrock":"☘️","four_leaf_clover":"🍀","maple_leaf":"🍁","fallen_leaf":"🍂","leaves":"🍃","grapes":"🍇","melon":"🍈","watermelon":"🍉","tangerine":"🍊","orange":"🍊","mandarin":"🍊","lemon":"🍋","banana":"🍌","pineapple":"🍍","mango":"🥭","apple":"🍎","green_apple":"🍏","pear":"🍐","peach":"🍑","cherries":"🍒","strawberry":"🍓","blueberries":"🫐","kiwi_fruit":"🥝","tomato":"🍅","olive":"🫒","coconut":"🥥","avocado":"🥑","eggplant":"🍆","potato":"🥔","carrot":"🥕","corn":"🌽","hot_pepper":"🌶️","bell_pepper":"🫑","cucumber":"🥒","leafy_green":"🥬","broccoli":"🥦","garlic":"🧄","onion":"🧅","mushroom":"🍄","peanuts":"🥜","chestnut":"🌰","bread":"🍞","croissant":"🥐","baguette_bread":"🥖","flatbread":"🫓","pretzel":"🥨","bagel":"🥯","pancakes":"🥞","waffle":"🧇","cheese":"🧀","meat_on_bone":"🍖","poultry_leg":"🍗","cut_of_meat":"🥩","bacon":"🥓","hamburger":"🍔","fries":"🍟","pizza":"🍕","hotdog":"🌭","sandwich":"🥪","taco":"🌮","burrito":"🌯","tamale":"🫔","stuffed_flatbread":"🥙","falafel":"🧆","egg":"🥚","fried_egg":"🍳","shallow_pan_of_food":"🥘","stew":"🍲","fondue":"🫕","bowl_with_spoon":"🥣","green_salad":"🥗","popcorn":"🍿","butter":"🧈","salt":"🧂","canned_food":"🥫","bento":"🍱","rice_cracker":"🍘","rice_ball":"🍙","rice":"🍚","curry":"🍛","ramen":"🍜","spaghetti":"🍝","sweet_potato":"🍠","oden":"🍢","sushi":"🍣","fried_shrimp":"🍤","fish_cake":"🍥","moon_cake":"🥮","dango":"🍡","dumpling":"🥟","fortune_cookie":"🥠","takeout_box":"🥡","crab":"🦀","lobster":"🦞","shrimp":"🦐","squid":"🦑","oyster":"🦪","icecream":"🍦","shaved_ice":"🍧","ice_cream":"🍨","doughnut":"🍩","cookie":"🍪","birthday":"🎂","cake":"🍰","cupcake":"🧁","pie":"🥧","chocolate_bar":"🍫","candy":"🍬","lollipop":"🍭","custard":"🍮","honey_pot":"🍯","baby_bottle":"🍼","milk_glass":"🥛","coffee":"☕","teapot":"🫖","tea":"🍵","sake":"🍶","champagne":"🍾","wine_glass":"🍷","cocktail":"🍸","tropical_drink":"🍹","beer":"🍺","beers":"🍻","clinking_glasses":"🥂","tumbler_glass":"🥃","cup_with_straw":"🥤","bubble_tea":"🧋","beverage_box":"🧃","mate":"🧉","ice_cube":"🧊","chopsticks":"🥢","plate_with_cutlery":"🍽️","fork_and_knife":"🍴","spoon":"🥄","hocho":"🔪","knife":"🔪","amphora":"🏺","earth_africa":"🌍","earth_americas":"🌎","earth_asia":"🌏","globe_with_meridians":"🌐","world_map":"🗺️","japan":"🗾","compass":"🧭","mountain_snow":"🏔️","mountain":"⛰️","volcano":"🌋","mount_fuji":"🗻","camping":"🏕️","beach_umbrella":"🏖️","desert":"🏜️","desert_island":"🏝️","national_park":"🏞️","stadium":"🏟️","classical_building":"🏛️","building_construction":"🏗️","bricks":"🧱","rock":"🪨","wood":"🪵","hut":"🛖","houses":"🏘️","derelict_house":"🏚️","house":"🏠","house_with_garden":"🏡","office":"🏢","post_office":"🏣","european_post_office":"🏤","hospital":"🏥","bank":"🏦","hotel":"🏨","love_hotel":"🏩","convenience_store":"🏪","school":"🏫","department_store":"🏬","factory":"🏭","japanese_castle":"🏯","european_castle":"🏰","wedding":"💒","tokyo_tower":"🗼","statue_of_liberty":"🗽","church":"⛪","mosque":"🕌","hindu_temple":"🛕","synagogue":"🕍","shinto_shrine":"⛩️","kaaba":"🕋","fountain":"⛲","tent":"⛺","foggy":"🌁","night_with_stars":"🌃","cityscape":"🏙️","sunrise_over_mountains":"🌄","sunrise":"🌅","city_sunset":"🌆","city_sunrise":"🌇","bridge_at_night":"🌉","hotsprings":"♨️","carousel_horse":"🎠","ferris_wheel":"🎡","roller_coaster":"🎢","barber":"💈","circus_tent":"🎪","steam_locomotive":"🚂","railway_car":"🚃","bullettrain_side":"🚄","bullettrain_front":"🚅","train2":"🚆","metro":"🚇","light_rail":"🚈","station":"🚉","tram":"🚊","monorail":"🚝","mountain_railway":"🚞","train":"🚋","bus":"🚌","oncoming_bus":"🚍","trolleybus":"🚎","minibus":"🚐","ambulance":"🚑","fire_engine":"🚒","police_car":"🚓","oncoming_police_car":"🚔","taxi":"🚕","oncoming_taxi":"🚖","car":"🚗","red_car":"🚗","oncoming_automobile":"🚘","blue_car":"🚙","pickup_truck":"🛻","truck":"🚚","articulated_lorry":"🚛","tractor":"🚜","racing_car":"🏎️","motorcycle":"🏍️","motor_scooter":"🛵","manual_wheelchair":"🦽","motorized_wheelchair":"🦼","auto_rickshaw":"🛺","bike":"🚲","kick_scooter":"🛴","skateboard":"🛹","roller_skate":"🛼","busstop":"🚏","motorway":"🛣️","railway_track":"🛤️","oil_drum":"🛢️","fuelpump":"⛽","rotating_light":"🚨","traffic_light":"🚥","vertical_traffic_light":"🚦","stop_sign":"🛑","construction":"🚧","anchor":"⚓","boat":"⛵","sailboat":"⛵","canoe":"🛶","speedboat":"🚤","passenger_ship":"🛳️","ferry":"⛴️","motor_boat":"🛥️","ship":"🚢","airplane":"✈️","small_airplane":"🛩️","flight_departure":"🛫","flight_arrival":"🛬","parachute":"🪂","seat":"💺","helicopter":"🚁","suspension_railway":"🚟","mountain_cableway":"🚠","aerial_tramway":"🚡","artificial_satellite":"🛰️","rocket":"🚀","flying_saucer":"🛸","bellhop_bell":"🛎️","luggage":"🧳","hourglass":"⌛","hourglass_flowing_sand":"⏳","watch":"⌚","alarm_clock":"⏰","stopwatch":"⏱️","timer_clock":"⏲️","mantelpiece_clock":"🕰️","clock12":"🕛","clock1230":"🕧","clock1":"🕐","clock130":"🕜","clock2":"🕑","clock230":"🕝","clock3":"🕒","clock330":"🕞","clock4":"🕓","clock430":"🕟","clock5":"🕔","clock530":"🕠","clock6":"🕕","clock630":"🕡","clock7":"🕖","clock730":"🕢","clock8":"🕗","clock830":"🕣","clock9":"🕘","clock930":"🕤","clock10":"🕙","clock1030":"🕥","clock11":"🕚","clock1130":"🕦","new_moon":"🌑","waxing_crescent_moon":"🌒","first_quarter_moon":"🌓","moon":"🌔","waxing_gibbous_moon":"🌔","full_moon":"🌕","waning_gibbous_moon":"🌖","last_quarter_moon":"🌗","waning_crescent_moon":"🌘","crescent_moon":"🌙","new_moon_with_face":"🌚","first_quarter_moon_with_face":"🌛","last_quarter_moon_with_face":"🌜","thermometer":"🌡️","sunny":"☀️","full_moon_with_face":"🌝","sun_with_face":"🌞","ringed_planet":"🪐","star":"⭐","star2":"🌟","stars":"🌠","milky_way":"🌌","cloud":"☁️","partly_sunny":"⛅","cloud_with_lightning_and_rain":"⛈️","sun_behind_small_cloud":"🌤️","sun_behind_large_cloud":"🌥️","sun_behind_rain_cloud":"🌦️","cloud_with_rain":"🌧️","cloud_with_snow":"🌨️","cloud_with_lightning":"🌩️","tornado":"🌪️","fog":"🌫️","wind_face":"🌬️","cyclone":"🌀","rainbow":"🌈","closed_umbrella":"🌂","open_umbrella":"☂️","umbrella":"☔","parasol_on_ground":"⛱️","zap":"⚡","snowflake":"❄️","snowman_with_snow":"☃️","snowman":"⛄","comet":"☄️","fire":"🔥","droplet":"💧","ocean":"🌊","jack_o_lantern":"🎃","christmas_tree":"🎄","fireworks":"🎆","sparkler":"🎇","firecracker":"🧨","sparkles":"✨","balloon":"🎈","tada":"🎉","confetti_ball":"🎊","tanabata_tree":"🎋","bamboo":"🎍","dolls":"🎎","flags":"🎏","wind_chime":"🎐","rice_scene":"🎑","red_envelope":"🧧","ribbon":"🎀","gift":"🎁","reminder_ribbon":"🎗️","tickets":"🎟️","ticket":"🎫","medal_military":"🎖️","trophy":"🏆","medal_sports":"🏅","1st_place_medal":"🥇","2nd_place_medal":"🥈","3rd_place_medal":"🥉","soccer":"⚽","baseball":"⚾","softball":"🥎","basketball":"🏀","volleyball":"🏐","football":"🏈","rugby_football":"🏉","tennis":"🎾","flying_disc":"🥏","bowling":"🎳","cricket_game":"🏏","field_hockey":"🏑","ice_hockey":"🏒","lacrosse":"🥍","ping_pong":"🏓","badminton":"🏸","boxing_glove":"🥊","martial_arts_uniform":"🥋","goal_net":"🥅","golf":"⛳","ice_skate":"⛸️","fishing_pole_and_fish":"🎣","diving_mask":"🤿","running_shirt_with_sash":"🎽","ski":"🎿","sled":"🛷","curling_stone":"🥌","dart":"🎯","yo_yo":"🪀","kite":"🪁","8ball":"🎱","crystal_ball":"🔮","magic_wand":"🪄","nazar_amulet":"🧿","video_game":"🎮","joystick":"🕹️","slot_machine":"🎰","game_die":"🎲","jigsaw":"🧩","teddy_bear":"🧸","pinata":"🪅","nesting_dolls":"🪆","spades":"♠️","hearts":"♥️","diamonds":"♦️","clubs":"♣️","chess_pawn":"♟️","black_joker":"🃏","mahjong":"🀄","flower_playing_cards":"🎴","performing_arts":"🎭","framed_picture":"🖼️","art":"🎨","thread":"🧵","sewing_needle":"🪡","yarn":"🧶","knot":"🪢","eyeglasses":"👓","dark_sunglasses":"🕶️","goggles":"🥽","lab_coat":"🥼","safety_vest":"🦺","necktie":"👔","shirt":"👕","tshirt":"👕","jeans":"👖","scarf":"🧣","gloves":"🧤","coat":"🧥","socks":"🧦","dress":"👗","kimono":"👘","sari":"🥻","one_piece_swimsuit":"🩱","swim_brief":"🩲","shorts":"🩳","bikini":"👙","womans_clothes":"👚","purse":"👛","handbag":"👜","pouch":"👝","shopping":"🛍️","school_satchel":"🎒","thong_sandal":"🩴","mans_shoe":"👞","shoe":"👞","athletic_shoe":"👟","hiking_boot":"🥾","flat_shoe":"🥿","high_heel":"👠","sandal":"👡","ballet_shoes":"🩰","boot":"👢","crown":"👑","womans_hat":"👒","tophat":"🎩","mortar_board":"🎓","billed_cap":"🧢","military_helmet":"🪖","rescue_worker_helmet":"⛑️","prayer_beads":"📿","lipstick":"💄","ring":"💍","gem":"💎","mute":"🔇","speaker":"🔈","sound":"🔉","loud_sound":"🔊","loudspeaker":"📢","mega":"📣","postal_horn":"📯","bell":"🔔","no_bell":"🔕","musical_score":"🎼","musical_note":"🎵","notes":"🎶","studio_microphone":"🎙️","level_slider":"🎚️","control_knobs":"🎛️","microphone":"🎤","headphones":"🎧","radio":"📻","saxophone":"🎷","accordion":"🪗","guitar":"🎸","musical_keyboard":"🎹","trumpet":"🎺","violin":"🎻","banjo":"🪕","drum":"🥁","long_drum":"🪘","iphone":"📱","calling":"📲","phone":"☎️","telephone":"☎️","telephone_receiver":"📞","pager":"📟","fax":"📠","battery":"🔋","electric_plug":"🔌","computer":"💻","desktop_computer":"🖥️","printer":"🖨️","keyboard":"⌨️","computer_mouse":"🖱️","trackball":"🖲️","minidisc":"💽","floppy_disk":"💾","cd":"💿","dvd":"📀","abacus":"🧮","movie_camera":"🎥","film_strip":"🎞️","film_projector":"📽️","clapper":"🎬","tv":"📺","camera":"📷","camera_flash":"📸","video_camera":"📹","vhs":"📼","mag":"🔍","mag_right":"🔎","candle":"🕯️","bulb":"💡","flashlight":"🔦","izakaya_lantern":"🏮","lantern":"🏮","diya_lamp":"🪔","notebook_with_decorative_cover":"📔","closed_book":"📕","book":"📖","open_book":"📖","green_book":"📗","blue_book":"📘","orange_book":"📙","books":"📚","notebook":"📓","ledger":"📒","page_with_curl":"📃","scroll":"📜","page_facing_up":"📄","newspaper":"📰","newspaper_roll":"🗞️","bookmark_tabs":"📑","bookmark":"🔖","label":"🏷️","moneybag":"💰","coin":"🪙","yen":"💴","dollar":"💵","euro":"💶","pound":"💷","money_with_wings":"💸","credit_card":"💳","receipt":"🧾","chart":"💹","envelope":"✉️","email":"📧","e-mail":"📧","incoming_envelope":"📨","envelope_with_arrow":"📩","outbox_tray":"📤","inbox_tray":"📥","package":"📦","mailbox":"📫","mailbox_closed":"📪","mailbox_with_mail":"📬","mailbox_with_no_mail":"📭","postbox":"📮","ballot_box":"🗳️","pencil2":"✏️","black_nib":"✒️","fountain_pen":"🖋️","pen":"🖊️","paintbrush":"🖌️","crayon":"🖍️","memo":"📝","pencil":"📝","briefcase":"💼","file_folder":"📁","open_file_folder":"📂","card_index_dividers":"🗂️","date":"📅","calendar":"📆","spiral_notepad":"🗒️","spiral_calendar":"🗓️","card_index":"📇","chart_with_upwards_trend":"📈","chart_with_downwards_trend":"📉","bar_chart":"📊","clipboard":"📋","pushpin":"📌","round_pushpin":"📍","paperclip":"📎","paperclips":"🖇️","straight_ruler":"📏","triangular_ruler":"📐","scissors":"✂️","card_file_box":"🗃️","file_cabinet":"🗄️","wastebasket":"🗑️","lock":"🔒","unlock":"🔓","lock_with_ink_pen":"🔏","closed_lock_with_key":"🔐","key":"🔑","old_key":"🗝️","hammer":"🔨","axe":"🪓","pick":"⛏️","hammer_and_pick":"⚒️","hammer_and_wrench":"🛠️","dagger":"🗡️","crossed_swords":"⚔️","gun":"🔫","boomerang":"🪃","bow_and_arrow":"🏹","shield":"🛡️","carpentry_saw":"🪚","wrench":"🔧","screwdriver":"🪛","nut_and_bolt":"🔩","gear":"⚙️","clamp":"🗜️","balance_scale":"⚖️","probing_cane":"🦯","link":"🔗","chains":"⛓️","hook":"🪝","toolbox":"🧰","magnet":"🧲","ladder":"🪜","alembic":"⚗️","test_tube":"🧪","petri_dish":"🧫","dna":"🧬","microscope":"🔬","telescope":"🔭","satellite":"📡","syringe":"💉","drop_of_blood":"🩸","pill":"💊","adhesive_bandage":"🩹","stethoscope":"🩺","door":"🚪","elevator":"🛗","mirror":"🪞","window":"🪟","bed":"🛏️","couch_and_lamp":"🛋️","chair":"🪑","toilet":"🚽","plunger":"🪠","shower":"🚿","bathtub":"🛁","mouse_trap":"🪤","razor":"🪒","lotion_bottle":"🧴","safety_pin":"🧷","broom":"🧹","basket":"🧺","roll_of_paper":"🧻","bucket":"🪣","soap":"🧼","toothbrush":"🪥","sponge":"🧽","fire_extinguisher":"🧯","shopping_cart":"🛒","smoking":"🚬","coffin":"⚰️","headstone":"🪦","funeral_urn":"⚱️","moyai":"🗿","placard":"🪧","atm":"🏧","put_litter_in_its_place":"🚮","potable_water":"🚰","wheelchair":"♿","mens":"🚹","womens":"🚺","restroom":"🚻","baby_symbol":"🚼","wc":"🚾","passport_control":"🛂","customs":"🛃","baggage_claim":"🛄","left_luggage":"🛅","warning":"⚠️","children_crossing":"🚸","no_entry":"⛔","no_entry_sign":"🚫","no_bicycles":"🚳","no_smoking":"🚭","do_not_litter":"🚯","non-potable_water":"🚱","no_pedestrians":"🚷","no_mobile_phones":"📵","underage":"🔞","radioactive":"☢️","biohazard":"☣️","arrow_up":"⬆️","arrow_upper_right":"↗️","arrow_right":"➡️","arrow_lower_right":"↘️","arrow_down":"⬇️","arrow_lower_left":"↙️","arrow_left":"⬅️","arrow_upper_left":"↖️","arrow_up_down":"↕️","left_right_arrow":"↔️","leftwards_arrow_with_hook":"↩️","arrow_right_hook":"↪️","arrow_heading_up":"⤴️","arrow_heading_down":"⤵️","arrows_clockwise":"🔃","arrows_counterclockwise":"🔄","back":"🔙","end":"🔚","on":"🔛","soon":"🔜","top":"🔝","place_of_worship":"🛐","atom_symbol":"⚛️","om":"🕉️","star_of_david":"✡️","wheel_of_dharma":"☸️","yin_yang":"☯️","latin_cross":"✝️","orthodox_cross":"☦️","star_and_crescent":"☪️","peace_symbol":"☮️","menorah":"🕎","six_pointed_star":"🔯","aries":"♈","taurus":"♉","gemini":"♊","cancer":"♋","leo":"♌","virgo":"♍","libra":"♎","scorpius":"♏","sagittarius":"♐","capricorn":"♑","aquarius":"♒","pisces":"♓","ophiuchus":"⛎","twisted_rightwards_arrows":"🔀","repeat":"🔁","repeat_one":"🔂","arrow_forward":"▶️","fast_forward":"⏩","next_track_button":"⏭️","play_or_pause_button":"⏯️","arrow_backward":"◀️","rewind":"⏪","previous_track_button":"⏮️","arrow_up_small":"🔼","arrow_double_up":"⏫","arrow_down_small":"🔽","arrow_double_down":"⏬","pause_button":"⏸️","stop_button":"⏹️","record_button":"⏺️","eject_button":"⏏️","cinema":"🎦","low_brightness":"🔅","high_brightness":"🔆","signal_strength":"📶","vibration_mode":"📳","mobile_phone_off":"📴","female_sign":"♀️","male_sign":"♂️","transgender_symbol":"⚧️","heavy_multiplication_x":"✖️","heavy_plus_sign":"➕","heavy_minus_sign":"➖","heavy_division_sign":"➗","infinity":"♾️","bangbang":"‼️","interrobang":"⁉️","question":"❓","grey_question":"❔","grey_exclamation":"❕","exclamation":"❗","heavy_exclamation_mark":"❗","wavy_dash":"〰️","currency_exchange":"💱","heavy_dollar_sign":"💲","medical_symbol":"⚕️","recycle":"♻️","fleur_de_lis":"⚜️","trident":"🔱","name_badge":"📛","beginner":"🔰","o":"⭕","white_check_mark":"✅","ballot_box_with_check":"☑️","heavy_check_mark":"✔️","x":"❌","negative_squared_cross_mark":"❎","curly_loop":"➰","loop":"➿","part_alternation_mark":"〽️","eight_spoked_asterisk":"✳️","eight_pointed_black_star":"✴️","sparkle":"❇️","copyright":"©️","registered":"®️","tm":"™️","hash":"#️⃣","asterisk":"*️⃣","zero":"0️⃣","one":"1️⃣","two":"2️⃣","three":"3️⃣","four":"4️⃣","five":"5️⃣","six":"6️⃣","seven":"7️⃣","eight":"8️⃣","nine":"9️⃣","keycap_ten":"🔟","capital_abcd":"🔠","abcd":"🔡","symbols":"🔣","abc":"🔤","a":"🅰️","ab":"🆎","b":"🅱️","cl":"🆑","cool":"🆒","free":"🆓","information_source":"ℹ️","id":"🆔","m":"Ⓜ️","new":"🆕","ng":"🆖","o2":"🅾️","ok":"🆗","parking":"🅿️","sos":"🆘","up":"🆙","vs":"🆚","koko":"🈁","sa":"🈂️","ideograph_advantage":"🉐","accept":"🉑","congratulations":"㊗️","secret":"㊙️","u6e80":"🈵","red_circle":"🔴","orange_circle":"🟠","yellow_circle":"🟡","green_circle":"🟢","large_blue_circle":"🔵","purple_circle":"🟣","brown_circle":"🟤","black_circle":"⚫","white_circle":"⚪","red_square":"🟥","orange_square":"🟧","yellow_square":"🟨","green_square":"🟩","blue_square":"🟦","purple_square":"🟪","brown_square":"🟫","black_large_square":"⬛","white_large_square":"⬜","black_medium_square":"◼️","white_medium_square":"◻️","black_medium_small_square":"◾","white_medium_small_square":"◽","black_small_square":"▪️","white_small_square":"▫️","large_orange_diamond":"🔶","large_blue_diamond":"🔷","small_orange_diamond":"🔸","small_blue_diamond":"🔹","small_red_triangle":"🔺","small_red_triangle_down":"🔻","diamond_shape_with_a_dot_inside":"💠","radio_button":"🔘","white_square_button":"🔳","black_square_button":"🔲","checkered_flag":"🏁","triangular_flag_on_post":"🚩","crossed_flags":"🎌","black_flag":"🏴","white_flag":"🏳️","rainbow_flag":"🏳️‍🌈","transgender_flag":"🏳️‍⚧️","pirate_flag":"🏴‍☠️","ascension_island":"🇦🇨","andorra":"🇦🇩","united_arab_emirates":"🇦🇪","afghanistan":"🇦🇫","antigua_barbuda":"🇦🇬","anguilla":"🇦🇮","albania":"🇦🇱","armenia":"🇦🇲","angola":"🇦🇴","antarctica":"🇦🇶","argentina":"🇦🇷","american_samoa":"🇦🇸","austria":"🇦🇹","australia":"🇦🇺","aruba":"🇦🇼","aland_islands":"🇦🇽","azerbaijan":"🇦🇿","bosnia_herzegovina":"🇧🇦","barbados":"🇧🇧","bangladesh":"🇧🇩","belgium":"🇧🇪","burkina_faso":"🇧🇫","bulgaria":"🇧🇬","bahrain":"🇧🇭","burundi":"🇧🇮","benin":"🇧🇯","st_barthelemy":"🇧🇱","bermuda":"🇧🇲","brunei":"🇧🇳","bolivia":"🇧🇴","caribbean_netherlands":"🇧🇶","brazil":"🇧🇷","bahamas":"🇧🇸","bhutan":"🇧🇹","bouvet_island":"🇧🇻","botswana":"🇧🇼","belarus":"🇧🇾","belize":"🇧🇿","canada":"🇨🇦","cocos_islands":"🇨🇨","congo_kinshasa":"🇨🇩","central_african_republic":"🇨🇫","congo_brazzaville":"🇨🇬","switzerland":"🇨🇭","cote_divoire":"🇨🇮","cook_islands":"🇨🇰","chile":"🇨🇱","cameroon":"🇨🇲","cn":"🇨🇳","colombia":"🇨🇴","clipperton_island":"🇨🇵","costa_rica":"🇨🇷","cuba":"🇨🇺","cape_verde":"🇨🇻","curacao":"🇨🇼","christmas_island":"🇨🇽","cyprus":"🇨🇾","czech_republic":"🇨🇿","de":"🇩🇪","diego_garcia":"🇩🇬","djibouti":"🇩🇯","denmark":"🇩🇰","dominica":"🇩🇲","dominican_republic":"🇩🇴","algeria":"🇩🇿","ceuta_melilla":"🇪🇦","ecuador":"🇪🇨","estonia":"🇪🇪","egypt":"🇪🇬","western_sahara":"🇪🇭","eritrea":"🇪🇷","es":"🇪🇸","ethiopia":"🇪🇹","eu":"🇪🇺","european_union":"🇪🇺","finland":"🇫🇮","fiji":"🇫🇯","falkland_islands":"🇫🇰","micronesia":"🇫🇲","faroe_islands":"🇫🇴","fr":"🇫🇷","gabon":"🇬🇦","gb":"🇬🇧","uk":"🇬🇧","grenada":"🇬🇩","georgia":"🇬🇪","french_guiana":"🇬🇫","guernsey":"🇬🇬","ghana":"🇬🇭","gibraltar":"🇬🇮","greenland":"🇬🇱","gambia":"🇬🇲","guinea":"🇬🇳","guadeloupe":"🇬🇵","equatorial_guinea":"🇬🇶","greece":"🇬🇷","south_georgia_south_sandwich_islands":"🇬🇸","guatemala":"🇬🇹","guam":"🇬🇺","guinea_bissau":"🇬🇼","guyana":"🇬🇾","hong_kong":"🇭🇰","heard_mcdonald_islands":"🇭🇲","honduras":"🇭🇳","croatia":"🇭🇷","haiti":"🇭🇹","hungary":"🇭🇺","canary_islands":"🇮🇨","indonesia":"🇮🇩","ireland":"🇮🇪","israel":"🇮🇱","isle_of_man":"🇮🇲","india":"🇮🇳","british_indian_ocean_territory":"🇮🇴","iraq":"🇮🇶","iran":"🇮🇷","iceland":"🇮🇸","it":"🇮🇹","jersey":"🇯🇪","jamaica":"🇯🇲","jordan":"🇯🇴","jp":"🇯🇵","kenya":"🇰🇪","kyrgyzstan":"🇰🇬","cambodia":"🇰🇭","kiribati":"🇰🇮","comoros":"🇰🇲","st_kitts_nevis":"🇰🇳","north_korea":"🇰🇵","kr":"🇰🇷","kuwait":"🇰🇼","cayman_islands":"🇰🇾","kazakhstan":"🇰🇿","laos":"🇱🇦","lebanon":"🇱🇧","st_lucia":"🇱🇨","liechtenstein":"🇱🇮","sri_lanka":"🇱🇰","liberia":"🇱🇷","lesotho":"🇱🇸","lithuania":"🇱🇹","luxembourg":"🇱🇺","latvia":"🇱🇻","libya":"🇱🇾","morocco":"🇲🇦","monaco":"🇲🇨","moldova":"🇲🇩","montenegro":"🇲🇪","st_martin":"🇲🇫","madagascar":"🇲🇬","marshall_islands":"🇲🇭","macedonia":"🇲🇰","mali":"🇲🇱","myanmar":"🇲🇲","mongolia":"🇲🇳","macau":"🇲🇴","northern_mariana_islands":"🇲🇵","martinique":"🇲🇶","mauritania":"🇲🇷","montserrat":"🇲🇸","malta":"🇲🇹","mauritius":"🇲🇺","maldives":"🇲🇻","malawi":"🇲🇼","mexico":"🇲🇽","malaysia":"🇲🇾","mozambique":"🇲🇿","namibia":"🇳🇦","new_caledonia":"🇳🇨","niger":"🇳🇪","norfolk_island":"🇳🇫","nigeria":"🇳🇬","nicaragua":"🇳🇮","netherlands":"🇳🇱","norway":"🇳🇴","nepal":"🇳🇵","nauru":"🇳🇷","niue":"🇳🇺","new_zealand":"🇳🇿","oman":"🇴🇲","panama":"🇵🇦","peru":"🇵🇪","french_polynesia":"🇵🇫","papua_new_guinea":"🇵🇬","philippines":"🇵🇭","pakistan":"🇵🇰","poland":"🇵🇱","st_pierre_miquelon":"🇵🇲","pitcairn_islands":"🇵🇳","puerto_rico":"🇵🇷","palestinian_territories":"🇵🇸","portugal":"🇵🇹","palau":"🇵🇼","paraguay":"🇵🇾","qatar":"🇶🇦","reunion":"🇷🇪","romania":"🇷🇴","serbia":"🇷🇸","ru":"🇷🇺","rwanda":"🇷🇼","saudi_arabia":"🇸🇦","solomon_islands":"🇸🇧","seychelles":"🇸🇨","sudan":"🇸🇩","sweden":"🇸🇪","singapore":"🇸🇬","st_helena":"🇸🇭","slovenia":"🇸🇮","svalbard_jan_mayen":"🇸🇯","slovakia":"🇸🇰","sierra_leone":"🇸🇱","san_marino":"🇸🇲","senegal":"🇸🇳","somalia":"🇸🇴","suriname":"🇸🇷","south_sudan":"🇸🇸","sao_tome_principe":"🇸🇹","el_salvador":"🇸🇻","sint_maarten":"🇸🇽","syria":"🇸🇾","swaziland":"🇸🇿","tristan_da_cunha":"🇹🇦","turks_caicos_islands":"🇹🇨","chad":"🇹🇩","french_southern_territories":"🇹🇫","togo":"🇹🇬","thailand":"🇹🇭","tajikistan":"🇹🇯","tokelau":"🇹🇰","timor_leste":"🇹🇱","turkmenistan":"🇹🇲","tunisia":"🇹🇳","tonga":"🇹🇴","tr":"🇹🇷","trinidad_tobago":"🇹🇹","tuvalu":"🇹🇻","taiwan":"🇹🇼","tanzania":"🇹🇿","ukraine":"🇺🇦","uganda":"🇺🇬","us_outlying_islands":"🇺🇲","united_nations":"🇺🇳","us":"🇺🇸","uruguay":"🇺🇾","uzbekistan":"🇺🇿","vatican_city":"🇻🇦","st_vincent_grenadines":"🇻🇨","venezuela":"🇻🇪","british_virgin_islands":"🇻🇬","us_virgin_islands":"🇻🇮","vietnam":"🇻🇳","vanuatu":"🇻🇺","wallis_futuna":"🇼🇫","samoa":"🇼🇸","kosovo":"🇽🇰","yemen":"🇾🇪","mayotte":"🇾🇹","south_africa":"🇿🇦","zambia":"🇿🇲","zimbabwe":"🇿🇼","england":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","scotland":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","wales":"🏴󠁧󠁢󠁷󠁬󠁳󠁿"}')},function(a,e,n){"use strict";a.exports={angry:[">:(",">:-("],blush:[':")',':-")'],broken_heart:["0&&!t.test(c[i-1]))return;if(i+o.lengths&&((_=new r("text","",0)).content=a.slice(s,i),l.push(_)),(_=new r("emoji","",0)).markup=m,_.content=e[m],l.push(_),s=i+o.length})),s=0;e--)"link_open"!==(t=_[e]).type&&"link_close"!==t.type||"auto"===t.info&&(c-=t.nesting),"text"===t.type&&0===c&&o.test(t.content)&&(l[n].children=_=r(_,e,s(t.content,t.level,a.Token)))}}},function(a,e,n){"use strict";a.exports=function(a){var e,n=a.defs;a.enabled.length&&(n=Object.keys(n).reduce((function(e,o){return a.enabled.indexOf(o)>=0&&(e[o]=n[o]),e}),{})),e=Object.keys(a.shortcuts).reduce((function(e,o){return n[o]?Array.isArray(a.shortcuts[o])?(a.shortcuts[o].forEach((function(a){e[a]=o})),e):(e[a.shortcuts[o]]=o,e):e}),{});var o,i=Object.keys(n);o=0===i.length?"^$":i.map((function(a){return":"+a+":"})).concat(Object.keys(e)).sort().reverse().map((function(a){return a.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")})).join("|");var r=RegExp(o),_=RegExp(o,"g");return{defs:n,shortcuts:e,scanRE:r,replaceRE:_}}}]); \ No newline at end of file diff --git a/extensions/notebook-markdown-extensions/notebook-out/katex.js b/extensions/notebook-markdown-extensions/notebook-out/katex.js deleted file mode 100644 index 117a960f9bf..00000000000 --- a/extensions/notebook-markdown-extensions/notebook-out/katex.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){var e={};function r(n){if(e[n])return e[n].exports;var a=e[n]={i:n,l:!1,exports:{}};return t[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var a in t)r.d(n,a,function(e){return t[e]}.bind(null,a));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,r){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const n=document.currentScript.src.replace("notebook-out/katex.js","")+"node_modules/katex/dist/katex.min.css",a=document.createElement("link");a.rel="stylesheet",a.href=n,document.head.append(a),function(){const t=r(1);"undefined"!=typeof extendMarkdownIt&&extendMarkdownIt(e=>{e.use(t)})}()},function(t,e,r){"use strict";var n=r(2);function a(t,e){var r,n,a=t.posMax,i=!0,o=!0;return r=e>0?t.src.charCodeAt(e-1):-1,n=e+1<=a?t.src.charCodeAt(e+1):-1,(32===r||9===r||n>=48&&n<=57)&&(o=!1),32!==n&&9!==n||(i=!1),{can_open:i,can_close:o}}function i(t,e){var r,n,i,o;if("$"!==t.src[t.pos])return!1;if(!a(t,t.pos).can_open)return e||(t.pending+="$"),t.pos+=1,!0;for(n=r=t.pos+1;-1!==(n=t.src.indexOf("$",n));){for(o=n-1;"\\"===t.src[o];)o-=1;if((n-o)%2==1)break;n+=1}return-1===n?(e||(t.pending+="$"),t.pos=r,!0):n-r==0?(e||(t.pending+="$$"),t.pos=r+1,!0):a(t,n).can_close?(e||((i=t.push("math_inline","math",0)).markup="$",i.content=t.src.slice(r,n)),t.pos=n+1,!0):(e||(t.pending+="$"),t.pos=r,!0)}function o(t,e,r,n){var a,i,o,s,l,h=!1,m=t.bMarks[e]+t.tShift[e],c=t.eMarks[e];if(m+2>c)return!1;if("$$"!==t.src.slice(m,m+2))return!1;if(m+=2,a=t.src.slice(m,c),n)return!0;for("$$"===a.trim().slice(-2)&&(a=a.trim().slice(0,-2),h=!0),o=e;!h&&!(++o>=r)&&!((m=t.bMarks[o]+t.tShift[o])<(c=t.eMarks[o])&&t.tShift[o]/g,">").replace(/"/g,""").replace(/'/g,"'")}t.exports=function(t,e){e=e||{};t.inline.ruler.after("escape","math_inline",i),t.block.ruler.after("blockquote","math_block",o,{alt:["paragraph","reference","blockquote","list"]}),t.renderer.rules.math_inline=function(t,r){return function(t){e.displayMode=!1;try{return n.renderToString(t,e)}catch(r){return e.throwOnError&&console.log(r),`${s(t)}`}}(t[r].content)},t.renderer.rules.math_block=function(t,r){return function(t){e.displayMode=!0;try{return"

"+n.renderToString(t,e)+"

"}catch(r){return e.throwOnError&&console.log(r),`

${s(t)}

`}}(t[r].content)+"\n"}}},function(t,e,r){var n;"undefined"!=typeof self&&self,n=function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var a=e[n]={i:n,l:!1,exports:{}};return t[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var a in t)r.d(n,a,function(e){return t[e]}.bind(null,a));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=1)}([function(t,e,r){},function(t,e,r){"use strict";r.r(e),r(0);var n=function(){function t(t,e,r){this.lexer=void 0,this.start=void 0,this.end=void 0,this.lexer=t,this.start=e,this.end=r}return t.range=function(e,r){return r?e&&e.loc&&r.loc&&e.loc.lexer===r.loc.lexer?new t(e.loc.lexer,e.loc.start,r.loc.end):null:e&&e.loc},t}(),a=function(){function t(t,e){this.text=void 0,this.loc=void 0,this.noexpand=void 0,this.treatAsRelax=void 0,this.text=t,this.loc=e}return t.prototype.range=function(e,r){return new t(r,n.range(this,e))},t}(),i=function t(e,r){this.position=void 0;var n,a="KaTeX parse error: "+e,i=r&&r.loc;if(i&&i.start<=i.end){var o=i.lexer.input;n=i.start;var s=i.end;n===o.length?a+=" at end of input: ":a+=" at position "+(n+1)+": ";var l=o.slice(n,s).replace(/[^]/g,"$&̲");a+=(n>15?"…"+o.slice(n-15,n):o.slice(0,n))+l+(s+15":">","<":"<",'"':""","'":"'"},h=/[&><"']/g,m=function t(e){return"ordgroup"===e.type||"color"===e.type?1===e.body.length?t(e.body[0]):e:"font"===e.type?t(e.body):e},c={contains:function(t,e){return-1!==t.indexOf(e)},deflt:function(t,e){return void 0===t?e:t},escape:function(t){return String(t).replace(h,(function(t){return l[t]}))},hyphenate:function(t){return t.replace(s,"-$1").toLowerCase()},getBaseElem:m,isCharacterBox:function(t){var e=m(t);return"mathord"===e.type||"textord"===e.type||"atom"===e.type},protocolFromUrl:function(t){var e=/^\s*([^\\/#]*?)(?::|�*58|�*3a)/i.exec(t);return null!=e?e[1]:"_relative"}},u=function(){function t(t){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.minRuleThickness=void 0,this.colorIsTextColor=void 0,this.strict=void 0,this.trust=void 0,this.maxSize=void 0,this.maxExpand=void 0,this.globalGroup=void 0,t=t||{},this.displayMode=c.deflt(t.displayMode,!1),this.output=c.deflt(t.output,"htmlAndMathml"),this.leqno=c.deflt(t.leqno,!1),this.fleqn=c.deflt(t.fleqn,!1),this.throwOnError=c.deflt(t.throwOnError,!0),this.errorColor=c.deflt(t.errorColor,"#cc0000"),this.macros=t.macros||{},this.minRuleThickness=Math.max(0,c.deflt(t.minRuleThickness,0)),this.colorIsTextColor=c.deflt(t.colorIsTextColor,!1),this.strict=c.deflt(t.strict,"warn"),this.trust=c.deflt(t.trust,!1),this.maxSize=Math.max(0,c.deflt(t.maxSize,1/0)),this.maxExpand=Math.max(0,c.deflt(t.maxExpand,1e3)),this.globalGroup=c.deflt(t.globalGroup,!1)}var e=t.prototype;return e.reportNonstrict=function(t,e,r){var n=this.strict;if("function"==typeof n&&(n=n(t,e,r)),n&&"ignore"!==n){if(!0===n||"error"===n)throw new o("LaTeX-incompatible input and strict mode is set to 'error': "+e+" ["+t+"]",r);"warn"===n?"undefined"!=typeof console&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+e+" ["+t+"]"):"undefined"!=typeof console&&console.warn("LaTeX-incompatible input and strict mode is set to unrecognized '"+n+"': "+e+" ["+t+"]")}},e.useStrictBehavior=function(t,e,r){var n=this.strict;if("function"==typeof n)try{n=n(t,e,r)}catch(t){n="error"}return!(!n||"ignore"===n||!0!==n&&"error"!==n&&("warn"===n?("undefined"!=typeof console&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+e+" ["+t+"]"),1):("undefined"!=typeof console&&console.warn("LaTeX-incompatible input and strict mode is set to unrecognized '"+n+"': "+e+" ["+t+"]"),1)))},e.isTrusted=function(t){t.url&&!t.protocol&&(t.protocol=c.protocolFromUrl(t.url));var e="function"==typeof this.trust?this.trust(t):this.trust;return Boolean(e)},t}(),p=function(){function t(t,e,r){this.id=void 0,this.size=void 0,this.cramped=void 0,this.id=t,this.size=e,this.cramped=r}var e=t.prototype;return e.sup=function(){return d[f[this.id]]},e.sub=function(){return d[g[this.id]]},e.fracNum=function(){return d[x[this.id]]},e.fracDen=function(){return d[v[this.id]]},e.cramp=function(){return d[b[this.id]]},e.text=function(){return d[y[this.id]]},e.isTight=function(){return this.size>=2},t}(),d=[new p(0,0,!1),new p(1,0,!0),new p(2,1,!1),new p(3,1,!0),new p(4,2,!1),new p(5,2,!0),new p(6,3,!1),new p(7,3,!0)],f=[4,5,4,5,6,7,6,7],g=[5,5,5,5,7,7,7,7],x=[2,3,4,5,6,7,6,7],v=[3,3,5,5,7,7,7,7],b=[1,1,3,3,5,5,7,7],y=[0,1,2,3,2,3,2,3],w={DISPLAY:d[0],TEXT:d[2],SCRIPT:d[4],SCRIPTSCRIPT:d[6]},k=[{name:"latin",blocks:[[256,591],[768,879]]},{name:"cyrillic",blocks:[[1024,1279]]},{name:"brahmic",blocks:[[2304,4255]]},{name:"georgian",blocks:[[4256,4351]]},{name:"cjk",blocks:[[12288,12543],[19968,40879],[65280,65376]]},{name:"hangul",blocks:[[44032,55215]]}],S=[];function M(t){for(var e=0;e=S[e]&&t<=S[e+1])return!0;return!1}k.forEach((function(t){return t.blocks.forEach((function(t){return S.push.apply(S,t)}))}));var z={leftParenInner:"M291 0 H417 V300 H291 z",rightParenInner:"M457 0 H583 V300 H457 z",doubleleftarrow:"M262 157\nl10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3\n 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28\n 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5\nc2 1.7 6.3 3.5 13 5.5 68 17.3 128.2 47.8 180.5 91.5 52.3 43.7 93.8 96.2 124.5\n 157.5 9.3 8 15.3 12.3 18 13h6c12-.7 18-4 18-10 0-2-1.7-7-5-15-23.3-46-52-87\n-86-123l-10-10h399738v-40H218c328 0 0 0 0 0l-10-8c-26.7-20-65.7-43-117-69 2.7\n-2 6-3.7 10-5 36.7-16 72.3-37.3 107-64l10-8h399782v-40z\nm8 0v40h399730v-40zm0 194v40h399730v-40z",doublerightarrow:"M399738 392l\n-10 10c-34 36-62.7 77-86 123-3.3 8-5 13.3-5 16 0 5.3 6.7 8 20 8 7.3 0 12.2-.5\n 14.5-1.5 2.3-1 4.8-4.5 7.5-10.5 49.3-97.3 121.7-169.3 217-216 28-14 57.3-25 88\n-33 6.7-2 11-3.8 13-5.5 2-1.7 3-4.2 3-7.5s-1-5.8-3-7.5c-2-1.7-6.3-3.5-13-5.5-68\n-17.3-128.2-47.8-180.5-91.5-52.3-43.7-93.8-96.2-124.5-157.5-9.3-8-15.3-12.3-18\n-13h-6c-12 .7-18 4-18 10 0 2 1.7 7 5 15 23.3 46 52 87 86 123l10 10H0v40h399782\nc-328 0 0 0 0 0l10 8c26.7 20 65.7 43 117 69-2.7 2-6 3.7-10 5-36.7 16-72.3 37.3\n-107 64l-10 8H0v40zM0 157v40h399730v-40zm0 194v40h399730v-40z",leftarrow:"M400000 241H110l3-3c68.7-52.7 113.7-120\n 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8\n-5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247\nc-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208\n 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3\n 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202\n l-3-3h399890zM100 241v40h399900v-40z",leftbrace:"M6 548l-6-6v-35l6-11c56-104 135.3-181.3 238-232 57.3-28.7 117\n-45 179-50h399577v120H403c-43.3 7-81 15-113 26-100.7 33-179.7 91-237 174-2.7\n 5-6 9-10 13-.7 1-7.3 1-20 1H6z",leftbraceunder:"M0 6l6-6h17c12.688 0 19.313.3 20 1 4 4 7.313 8.3 10 13\n 35.313 51.3 80.813 93.8 136.5 127.5 55.688 33.7 117.188 55.8 184.5 66.5.688\n 0 2 .3 4 1 18.688 2.7 76 4.3 172 5h399450v120H429l-6-1c-124.688-8-235-61.7\n-331-161C60.687 138.7 32.312 99.3 7 54L0 41V6z",leftgroup:"M400000 80\nH435C64 80 168.3 229.4 21 260c-5.9 1.2-18 0-18 0-2 0-3-1-3-3v-38C76 61 257 0\n 435 0h399565z",leftgroupunder:"M400000 262\nH435C64 262 168.3 112.6 21 82c-5.9-1.2-18 0-18 0-2 0-3 1-3 3v38c76 158 257 219\n 435 219h399565z",leftharpoon:"M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3\n-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5\n-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7\n-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40z",leftharpoonplus:"M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5\n 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3\n-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7\n-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40zM0 435v40h400000v-40z\nm0 0v40h400000v-40z",leftharpoondown:"M7 241c-4 4-6.333 8.667-7 14 0 5.333.667 9 2 11s5.333\n 5.333 12 10c90.667 54 156 130 196 228 3.333 10.667 6.333 16.333 9 17 2 .667 5\n 1 9 1h5c10.667 0 16.667-2 18-6 2-2.667 1-9.667-3-21-32-87.333-82.667-157.667\n-152-211l-3-3h399907v-40zM93 281 H400000 v-40L7 241z",leftharpoondownplus:"M7 435c-4 4-6.3 8.7-7 14 0 5.3.7 9 2 11s5.3 5.3 12\n 10c90.7 54 156 130 196 228 3.3 10.7 6.3 16.3 9 17 2 .7 5 1 9 1h5c10.7 0 16.7\n-2 18-6 2-2.7 1-9.7-3-21-32-87.3-82.7-157.7-152-211l-3-3h399907v-40H7zm93 0\nv40h399900v-40zM0 241v40h399900v-40zm0 0v40h399900v-40z",lefthook:"M400000 281 H103s-33-11.2-61-33.5S0 197.3 0 164s14.2-61.2 42.5\n-83.5C70.8 58.2 104 47 142 47 c16.7 0 25 6.7 25 20 0 12-8.7 18.7-26 20-40 3.3\n-68.7 15.7-86 37-10 12-15 25.3-15 40 0 22.7 9.8 40.7 29.5 54 19.7 13.3 43.5 21\n 71.5 23h399859zM103 281v-40h399897v40z",leftlinesegment:"M40 281 V428 H0 V94 H40 V241 H400000 v40z\nM40 281 V428 H0 V94 H40 V241 H400000 v40z",leftmapsto:"M40 281 V448H0V74H40V241H400000v40z\nM40 281 V448H0V74H40V241H400000v40z",leftToFrom:"M0 147h400000v40H0zm0 214c68 40 115.7 95.7 143 167h22c15.3 0 23\n-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h399905v-40H95l7-8\nc28.7-32 52-65.7 70-101 10.7-23.3 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 265.3\n 68 321 0 361zm0-174v-40h399900v40zm100 154v40h399900v-40z",longequal:"M0 50 h400000 v40H0z m0 194h40000v40H0z\nM0 50 h400000 v40H0z m0 194h40000v40H0z",midbrace:"M200428 334\nc-100.7-8.3-195.3-44-280-108-55.3-42-101.7-93-139-153l-9-14c-2.7 4-5.7 8.7-9 14\n-53.3 86.7-123.7 153-211 199-66.7 36-137.3 56.3-212 62H0V214h199568c178.3-11.7\n 311.7-78.3 403-201 6-8 9.7-12 11-12 .7-.7 6.7-1 18-1s17.3.3 18 1c1.3 0 5 4 11\n 12 44.7 59.3 101.3 106.3 170 141s145.3 54.3 229 60h199572v120z",midbraceunder:"M199572 214\nc100.7 8.3 195.3 44 280 108 55.3 42 101.7 93 139 153l9 14c2.7-4 5.7-8.7 9-14\n 53.3-86.7 123.7-153 211-199 66.7-36 137.3-56.3 212-62h199568v120H200432c-178.3\n 11.7-311.7 78.3-403 201-6 8-9.7 12-11 12-.7.7-6.7 1-18 1s-17.3-.3-18-1c-1.3 0\n-5-4-11-12-44.7-59.3-101.3-106.3-170-141s-145.3-54.3-229-60H0V214z",oiintSize1:"M512.6 71.6c272.6 0 320.3 106.8 320.3 178.2 0 70.8-47.7 177.6\n-320.3 177.6S193.1 320.6 193.1 249.8c0-71.4 46.9-178.2 319.5-178.2z\nm368.1 178.2c0-86.4-60.9-215.4-368.1-215.4-306.4 0-367.3 129-367.3 215.4 0 85.8\n60.9 214.8 367.3 214.8 307.2 0 368.1-129 368.1-214.8z",oiintSize2:"M757.8 100.1c384.7 0 451.1 137.6 451.1 230 0 91.3-66.4 228.8\n-451.1 228.8-386.3 0-452.7-137.5-452.7-228.8 0-92.4 66.4-230 452.7-230z\nm502.4 230c0-111.2-82.4-277.2-502.4-277.2s-504 166-504 277.2\nc0 110 84 276 504 276s502.4-166 502.4-276z",oiiintSize1:"M681.4 71.6c408.9 0 480.5 106.8 480.5 178.2 0 70.8-71.6 177.6\n-480.5 177.6S202.1 320.6 202.1 249.8c0-71.4 70.5-178.2 479.3-178.2z\nm525.8 178.2c0-86.4-86.8-215.4-525.7-215.4-437.9 0-524.7 129-524.7 215.4 0\n85.8 86.8 214.8 524.7 214.8 438.9 0 525.7-129 525.7-214.8z",oiiintSize2:"M1021.2 53c603.6 0 707.8 165.8 707.8 277.2 0 110-104.2 275.8\n-707.8 275.8-606 0-710.2-165.8-710.2-275.8C311 218.8 415.2 53 1021.2 53z\nm770.4 277.1c0-131.2-126.4-327.6-770.5-327.6S248.4 198.9 248.4 330.1\nc0 130 128.8 326.4 772.7 326.4s770.5-196.4 770.5-326.4z",rightarrow:"M0 241v40h399891c-47.3 35.3-84 78-110 128\n-16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20\n 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7\n 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85\n-40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5\n-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67\n 151.7 139 205zm0 0v40h399900v-40z",rightbrace:"M400000 542l\n-6 6h-17c-12.7 0-19.3-.3-20-1-4-4-7.3-8.3-10-13-35.3-51.3-80.8-93.8-136.5-127.5\ns-117.2-55.8-184.5-66.5c-.7 0-2-.3-4-1-18.7-2.7-76-4.3-172-5H0V214h399571l6 1\nc124.7 8 235 61.7 331 161 31.3 33.3 59.7 72.7 85 118l7 13v35z",rightbraceunder:"M399994 0l6 6v35l-6 11c-56 104-135.3 181.3-238 232-57.3\n 28.7-117 45-179 50H-300V214h399897c43.3-7 81-15 113-26 100.7-33 179.7-91 237\n-174 2.7-5 6-9 10-13 .7-1 7.3-1 20-1h17z",rightgroup:"M0 80h399565c371 0 266.7 149.4 414 180 5.9 1.2 18 0 18 0 2 0\n 3-1 3-3v-38c-76-158-257-219-435-219H0z",rightgroupunder:"M0 262h399565c371 0 266.7-149.4 414-180 5.9-1.2 18 0 18\n 0 2 0 3 1 3 3v38c-76 158-257 219-435 219H0z",rightharpoon:"M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3\n-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2\n-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58\n 69.2 92 94.5zm0 0v40h399900v-40z",rightharpoonplus:"M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11\n-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7\n 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5z\nm0 0v40h399900v-40z m100 194v40h399900v-40zm0 0v40h399900v-40z",rightharpoondown:"M399747 511c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8\n 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5\n-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95\n-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 241v40h399900v-40z",rightharpoondownplus:"M399747 705c0 7.3 6.7 11 20 11 8 0 13-.8\n 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3\n 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3\n-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 435v40h399900v-40z\nm0-194v40h400000v-40zm0 0v40h400000v-40z",righthook:"M399859 241c-764 0 0 0 0 0 40-3.3 68.7-15.7 86-37 10-12 15-25.3\n 15-40 0-22.7-9.8-40.7-29.5-54-19.7-13.3-43.5-21-71.5-23-17.3-1.3-26-8-26-20 0\n-13.3 8.7-20 26-20 38 0 71 11.2 99 33.5 0 0 7 5.6 21 16.7 14 11.2 21 33.5 21\n 66.8s-14 61.2-42 83.5c-28 22.3-61 33.5-99 33.5L0 241z M0 281v-40h399859v40z",rightlinesegment:"M399960 241 V94 h40 V428 h-40 V281 H0 v-40z\nM399960 241 V94 h40 V428 h-40 V281 H0 v-40z",rightToFrom:"M400000 167c-70.7-42-118-97.7-142-167h-23c-15.3 0-23 .3-23\n 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8H0v40h399905l-7 8c-28.7 32\n-52 65.7-70 101-10.7 23.3-16 35.7-16 37 0 .7 7.7 1 23 1h23c24-69.3 71.3-125 142\n-167z M100 147v40h399900v-40zM0 341v40h399900v-40z",twoheadleftarrow:"M0 167c68 40\n 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69\n-70-101l-7-8h125l9 7c50.7 39.3 85 86 103 140h46c0-4.7-6.3-18.7-19-42-18-35.3\n-40-67.3-66-96l-9-9h399716v-40H284l9-9c26-28.7 48-60.7 66-96 12.7-23.333 19\n-37.333 19-42h-46c-18 54-52.3 100.7-103 140l-9 7H95l7-8c28.7-32 52-65.7 70-101\n 10.7-23.333 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 71.3 68 127 0 167z",twoheadrightarrow:"M400000 167\nc-68-40-115.7-95.7-143-167h-22c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3\n 41.3 69 70 101l7 8h-125l-9-7c-50.7-39.3-85-86-103-140h-46c0 4.7 6.3 18.7 19 42\n 18 35.3 40 67.3 66 96l9 9H0v40h399716l-9 9c-26 28.7-48 60.7-66 96-12.7 23.333\n-19 37.333-19 42h46c18-54 52.3-100.7 103-140l9-7h125l-7 8c-28.7 32-52 65.7-70\n 101-10.7 23.333-16 35.7-16 37 0 .7 7.7 1 23 1h22c27.3-71.3 75-127 143-167z",tilde1:"M200 55.538c-77 0-168 73.953-177 73.953-3 0-7\n-2.175-9-5.437L2 97c-1-2-2-4-2-6 0-4 2-7 5-9l20-12C116 12 171 0 207 0c86 0\n 114 68 191 68 78 0 168-68 177-68 4 0 7 2 9 5l12 19c1 2.175 2 4.35 2 6.525 0\n 4.35-2 7.613-5 9.788l-19 13.05c-92 63.077-116.937 75.308-183 76.128\n-68.267.847-113-73.952-191-73.952z",tilde2:"M344 55.266c-142 0-300.638 81.316-311.5 86.418\n-8.01 3.762-22.5 10.91-23.5 5.562L1 120c-1-2-1-3-1-4 0-5 3-9 8-10l18.4-9C160.9\n 31.9 283 0 358 0c148 0 188 122 331 122s314-97 326-97c4 0 8 2 10 7l7 21.114\nc1 2.14 1 3.21 1 4.28 0 5.347-3 9.626-7 10.696l-22.3 12.622C852.6 158.372 751\n 181.476 676 181.476c-149 0-189-126.21-332-126.21z",tilde3:"M786 59C457 59 32 175.242 13 175.242c-6 0-10-3.457\n-11-10.37L.15 138c-1-7 3-12 10-13l19.2-6.4C378.4 40.7 634.3 0 804.3 0c337 0\n 411.8 157 746.8 157 328 0 754-112 773-112 5 0 10 3 11 9l1 14.075c1 8.066-.697\n 16.595-6.697 17.492l-21.052 7.31c-367.9 98.146-609.15 122.696-778.15 122.696\n -338 0-409-156.573-744-156.573z",tilde4:"M786 58C457 58 32 177.487 13 177.487c-6 0-10-3.345\n-11-10.035L.15 143c-1-7 3-12 10-13l22-6.7C381.2 35 637.15 0 807.15 0c337 0 409\n 177 744 177 328 0 754-127 773-127 5 0 10 3 11 9l1 14.794c1 7.805-3 13.38-9\n 14.495l-20.7 5.574c-366.85 99.79-607.3 139.372-776.3 139.372-338 0-409\n -175.236-744-175.236z",vec:"M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5\n3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11\n10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63\n-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1\n-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59\nH213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359\nc-16-25.333-24-45-24-59z",widehat1:"M529 0h5l519 115c5 1 9 5 9 10 0 1-1 2-1 3l-4 22\nc-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z",widehat2:"M1181 0h2l1171 176c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 220h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",widehat3:"M1181 0h2l1171 236c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 280h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",widehat4:"M1181 0h2l1171 296c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z",widecheck1:"M529,159h5l519,-115c5,-1,9,-5,9,-10c0,-1,-1,-2,-1,-3l-4,-22c-1,\n-5,-5,-9,-11,-9h-2l-512,92l-513,-92h-2c-5,0,-9,4,-11,9l-5,22c-1,6,2,12,8,13z",widecheck2:"M1181,220h2l1171,-176c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,153l-1167,-153h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",widecheck3:"M1181,280h2l1171,-236c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,213l-1167,-213h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",widecheck4:"M1181,340h2l1171,-296c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,273l-1167,-273h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z",baraboveleftarrow:"M400000 620h-399890l3 -3c68.7 -52.7 113.7 -120 135 -202\nc4 -14.7 6 -23 6 -25c0 -7.3 -7 -11 -21 -11c-8 0 -13.2 0.8 -15.5 2.5\nc-2.3 1.7 -4.2 5.8 -5.5 12.5c-1.3 4.7 -2.7 10.3 -4 17c-12 48.7 -34.8 92 -68.5 130\ns-74.2 66.3 -121.5 85c-10 4 -16 7.7 -18 11c0 8.7 6 14.3 18 17c47.3 18.7 87.8 47\n121.5 85s56.5 81.3 68.5 130c0.7 2 1.3 5 2 9s1.2 6.7 1.5 8c0.3 1.3 1 3.3 2 6\ns2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21 -3.7 21 -11\nc0 -2 -2 -10.3 -6 -25c-20 -79.3 -65 -146.7 -135 -202l-3 -3h399890z\nM100 620v40h399900v-40z M0 241v40h399900v-40zM0 241v40h399900v-40z",rightarrowabovebar:"M0 241v40h399891c-47.3 35.3-84 78-110 128-16.7 32\n-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0\n13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39\n-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85-40.5\n-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5\n-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67\n151.7 139 205zm96 379h399894v40H0zm0 0h399904v40H0z",baraboveshortleftharpoon:"M507,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11\nc1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17\nc2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21\nc-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399351,0l0,-40\nc-398570,0,-399437,0,-399437,0z M593 435 v40 H399500 v-40z\nM0 281 v-40 H399908 v40z M0 281 v-40 H399908 v40z",rightharpoonaboveshortbar:"M0,241 l0,40c399126,0,399993,0,399993,0\nc4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,\n-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6\nc-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z\nM0 241 v40 H399908 v-40z M0 475 v-40 H399500 v40z M0 475 v-40 H399500 v40z",shortbaraboveleftharpoon:"M7,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11\nc1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9,\n1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7,\n-152,-211c0,0,-3,-3,-3,-3l399907,0l0,-40c-399126,0,-399993,0,-399993,0z\nM93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z",shortrightharpoonabovebar:"M53,241l0,40c398570,0,399437,0,399437,0\nc4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,\n-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6\nc-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z\nM500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z"},A=function(){function t(t){this.children=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.children=t,this.classes=[],this.height=0,this.depth=0,this.maxFontSize=0,this.style={}}var e=t.prototype;return e.hasClass=function(t){return c.contains(this.classes,t)},e.toNode=function(){for(var t=document.createDocumentFragment(),e=0;e"},N=function(){function t(t,e,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.width=void 0,this.maxFontSize=void 0,this.style=void 0,B.call(this,t,r,n),this.children=e||[]}var e=t.prototype;return e.setAttribute=function(t,e){this.attributes[t]=e},e.hasClass=function(t){return c.contains(this.classes,t)},e.toNode=function(){return C.call(this,"span")},e.toMarkup=function(){return q.call(this,"span")},t}(),I=function(){function t(t,e,r,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,B.call(this,e,n),this.children=r||[],this.setAttribute("href",t)}var e=t.prototype;return e.setAttribute=function(t,e){this.attributes[t]=e},e.hasClass=function(t){return c.contains(this.classes,t)},e.toNode=function(){return C.call(this,"a")},e.toMarkup=function(){return q.call(this,"a")},t}(),O=function(){function t(t,e,r){this.src=void 0,this.alt=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.alt=e,this.src=t,this.classes=["mord"],this.style=r}var e=t.prototype;return e.hasClass=function(t){return c.contains(this.classes,t)},e.toNode=function(){var t=document.createElement("img");for(var e in t.src=this.src,t.alt=this.alt,t.className="mord",this.style)this.style.hasOwnProperty(e)&&(t.style[e]=this.style[e]);return t},e.toMarkup=function(){var t=""+this.alt+"=a[0]&&t<=a[1])return r.name}return null}(this.text.charCodeAt(0));l&&this.classes.push(l+"_fallback"),/[îïíì]/.test(this.text)&&(this.text=R[this.text])}var e=t.prototype;return e.hasClass=function(t){return c.contains(this.classes,t)},e.toNode=function(){var t=document.createTextNode(this.text),e=null;for(var r in this.italic>0&&((e=document.createElement("span")).style.marginRight=this.italic+"em"),this.classes.length>0&&((e=e||document.createElement("span")).className=T(this.classes)),this.style)this.style.hasOwnProperty(r)&&((e=e||document.createElement("span")).style[r]=this.style[r]);return e?(e.appendChild(t),e):t},e.toMarkup=function(){var t=!1,e="0&&(r+="margin-right:"+this.italic+"em;"),this.style)this.style.hasOwnProperty(n)&&(r+=c.hyphenate(n)+":"+this.style[n]+";");r&&(t=!0,e+=' style="'+c.escape(r)+'"');var a=c.escape(this.text);return t?(e+=">",e+=a,e+=""):a},t}(),L=function(){function t(t,e){this.children=void 0,this.attributes=void 0,this.children=t||[],this.attributes=e||{}}var e=t.prototype;return e.toNode=function(){var t=document.createElementNS("http://www.w3.org/2000/svg","svg");for(var e in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,e)&&t.setAttribute(e,this.attributes[e]);for(var r=0;r":""},t}(),D=function(){function t(t){this.attributes=void 0,this.attributes=t||{}}var e=t.prototype;return e.toNode=function(){var t=document.createElementNS("http://www.w3.org/2000/svg","line");for(var e in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,e)&&t.setAttribute(e,this.attributes[e]);return t},e.toMarkup=function(){var t="","\\gt",!0),j("math",Z,et,"∈","\\in",!0),j("math",Z,et,"","\\@not"),j("math",Z,et,"⊂","\\subset",!0),j("math",Z,et,"⊃","\\supset",!0),j("math",Z,et,"⊆","\\subseteq",!0),j("math",Z,et,"⊇","\\supseteq",!0),j("math",K,et,"⊈","\\nsubseteq",!0),j("math",K,et,"⊉","\\nsupseteq",!0),j("math",Z,et,"⊨","\\models"),j("math",Z,et,"←","\\leftarrow",!0),j("math",Z,et,"≤","\\le"),j("math",Z,et,"≤","\\leq",!0),j("math",Z,et,"<","\\lt",!0),j("math",Z,et,"→","\\rightarrow",!0),j("math",Z,et,"→","\\to"),j("math",K,et,"≱","\\ngeq",!0),j("math",K,et,"≰","\\nleq",!0),j("math",Z,"spacing"," ","\\ "),j("math",Z,"spacing"," ","~"),j("math",Z,"spacing"," ","\\space"),j("math",Z,"spacing"," ","\\nobreakspace"),j("text",Z,"spacing"," ","\\ "),j("text",Z,"spacing"," "," "),j("text",Z,"spacing"," ","~"),j("text",Z,"spacing"," ","\\space"),j("text",Z,"spacing"," ","\\nobreakspace"),j("math",Z,"spacing",null,"\\nobreak"),j("math",Z,"spacing",null,"\\allowbreak"),j("math",Z,"punct",",",","),j("math",Z,"punct",";",";"),j("math",K,J,"⊼","\\barwedge",!0),j("math",K,J,"⊻","\\veebar",!0),j("math",Z,J,"⊙","\\odot",!0),j("math",Z,J,"⊕","\\oplus",!0),j("math",Z,J,"⊗","\\otimes",!0),j("math",Z,"textord","∂","\\partial",!0),j("math",Z,J,"⊘","\\oslash",!0),j("math",K,J,"⊚","\\circledcirc",!0),j("math",K,J,"⊡","\\boxdot",!0),j("math",Z,J,"△","\\bigtriangleup"),j("math",Z,J,"▽","\\bigtriangledown"),j("math",Z,J,"†","\\dagger"),j("math",Z,J,"⋄","\\diamond"),j("math",Z,J,"⋆","\\star"),j("math",Z,J,"◃","\\triangleleft"),j("math",Z,J,"▹","\\triangleright"),j("math",Z,"open","{","\\{"),j("text",Z,"textord","{","\\{"),j("text",Z,"textord","{","\\textbraceleft"),j("math",Z,"close","}","\\}"),j("text",Z,"textord","}","\\}"),j("text",Z,"textord","}","\\textbraceright"),j("math",Z,"open","{","\\lbrace"),j("math",Z,"close","}","\\rbrace"),j("math",Z,"open","[","\\lbrack",!0),j("text",Z,"textord","[","\\lbrack",!0),j("math",Z,"close","]","\\rbrack",!0),j("text",Z,"textord","]","\\rbrack",!0),j("math",Z,"open","(","\\lparen",!0),j("math",Z,"close",")","\\rparen",!0),j("text",Z,"textord","<","\\textless",!0),j("text",Z,"textord",">","\\textgreater",!0),j("math",Z,"open","⌊","\\lfloor",!0),j("math",Z,"close","⌋","\\rfloor",!0),j("math",Z,"open","⌈","\\lceil",!0),j("math",Z,"close","⌉","\\rceil",!0),j("math",Z,"textord","\\","\\backslash"),j("math",Z,"textord","∣","|"),j("math",Z,"textord","∣","\\vert"),j("text",Z,"textord","|","\\textbar",!0),j("math",Z,"textord","∥","\\|"),j("math",Z,"textord","∥","\\Vert"),j("text",Z,"textord","∥","\\textbardbl"),j("text",Z,"textord","~","\\textasciitilde"),j("text",Z,"textord","\\","\\textbackslash"),j("text",Z,"textord","^","\\textasciicircum"),j("math",Z,et,"↑","\\uparrow",!0),j("math",Z,et,"⇑","\\Uparrow",!0),j("math",Z,et,"↓","\\downarrow",!0),j("math",Z,et,"⇓","\\Downarrow",!0),j("math",Z,et,"↕","\\updownarrow",!0),j("math",Z,et,"⇕","\\Updownarrow",!0),j("math",Z,tt,"∐","\\coprod"),j("math",Z,tt,"⋁","\\bigvee"),j("math",Z,tt,"⋀","\\bigwedge"),j("math",Z,tt,"⨄","\\biguplus"),j("math",Z,tt,"⋂","\\bigcap"),j("math",Z,tt,"⋃","\\bigcup"),j("math",Z,tt,"∫","\\int"),j("math",Z,tt,"∫","\\intop"),j("math",Z,tt,"∬","\\iint"),j("math",Z,tt,"∭","\\iiint"),j("math",Z,tt,"∏","\\prod"),j("math",Z,tt,"∑","\\sum"),j("math",Z,tt,"⨂","\\bigotimes"),j("math",Z,tt,"⨁","\\bigoplus"),j("math",Z,tt,"⨀","\\bigodot"),j("math",Z,tt,"∮","\\oint"),j("math",Z,tt,"⨆","\\bigsqcup"),j("math",Z,tt,"∫","\\smallint"),j("text",Z,"inner","…","\\textellipsis"),j("math",Z,"inner","…","\\mathellipsis"),j("text",Z,"inner","…","\\ldots",!0),j("math",Z,"inner","…","\\ldots",!0),j("math",Z,"inner","⋯","\\@cdots",!0),j("math",Z,"inner","⋱","\\ddots",!0),j("math",Z,"textord","⋮","\\varvdots"),j("math",Z,"accent-token","ˊ","\\acute"),j("math",Z,"accent-token","ˋ","\\grave"),j("math",Z,"accent-token","¨","\\ddot"),j("math",Z,"accent-token","~","\\tilde"),j("math",Z,"accent-token","ˉ","\\bar"),j("math",Z,"accent-token","˘","\\breve"),j("math",Z,"accent-token","ˇ","\\check"),j("math",Z,"accent-token","^","\\hat"),j("math",Z,"accent-token","⃗","\\vec"),j("math",Z,"accent-token","˙","\\dot"),j("math",Z,"accent-token","˚","\\mathring"),j("math",Z,Q,"","\\@imath"),j("math",Z,Q,"","\\@jmath"),j("math",Z,"textord","ı","ı"),j("math",Z,"textord","ȷ","ȷ"),j("text",Z,"textord","ı","\\i",!0),j("text",Z,"textord","ȷ","\\j",!0),j("text",Z,"textord","ß","\\ss",!0),j("text",Z,"textord","æ","\\ae",!0),j("text",Z,"textord","œ","\\oe",!0),j("text",Z,"textord","ø","\\o",!0),j("text",Z,"textord","Æ","\\AE",!0),j("text",Z,"textord","Œ","\\OE",!0),j("text",Z,"textord","Ø","\\O",!0),j("text",Z,"accent-token","ˊ","\\'"),j("text",Z,"accent-token","ˋ","\\`"),j("text",Z,"accent-token","ˆ","\\^"),j("text",Z,"accent-token","˜","\\~"),j("text",Z,"accent-token","ˉ","\\="),j("text",Z,"accent-token","˘","\\u"),j("text",Z,"accent-token","˙","\\."),j("text",Z,"accent-token","˚","\\r"),j("text",Z,"accent-token","ˇ","\\v"),j("text",Z,"accent-token","¨",'\\"'),j("text",Z,"accent-token","˝","\\H"),j("text",Z,"accent-token","◯","\\textcircled");var rt={"--":!0,"---":!0,"``":!0,"''":!0};j("text",Z,"textord","–","--",!0),j("text",Z,"textord","–","\\textendash"),j("text",Z,"textord","—","---",!0),j("text",Z,"textord","—","\\textemdash"),j("text",Z,"textord","‘","`",!0),j("text",Z,"textord","‘","\\textquoteleft"),j("text",Z,"textord","’","'",!0),j("text",Z,"textord","’","\\textquoteright"),j("text",Z,"textord","“","``",!0),j("text",Z,"textord","“","\\textquotedblleft"),j("text",Z,"textord","”","''",!0),j("text",Z,"textord","”","\\textquotedblright"),j("math",Z,"textord","°","\\degree",!0),j("text",Z,"textord","°","\\degree"),j("text",Z,"textord","°","\\textdegree",!0),j("math",Z,"textord","£","\\pounds"),j("math",Z,"textord","£","\\mathsterling",!0),j("text",Z,"textord","£","\\pounds"),j("text",Z,"textord","£","\\textsterling",!0),j("math",K,"textord","✠","\\maltese"),j("text",K,"textord","✠","\\maltese");for(var nt=0;nt<'0123456789/@."'.length;nt++){var at='0123456789/@."'.charAt(nt);j("math",Z,"textord",at,at)}for(var it=0;it<'0123456789!@*()-=+";:?/.,'.length;it++){var ot='0123456789!@*()-=+";:?/.,'.charAt(it);j("text",Z,"textord",ot,ot)}for(var st="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",lt=0;lt=5?0:t>=3?1:2]){var r=_[e]={cssEmPerMu:V.quad[e]/18};for(var n in V)V.hasOwnProperty(n)&&(r[n]=V[n][e])}return _[e]}(this.size)),this._fontMetrics},e.getColor=function(){return this.phantom?"transparent":this.color},t}();kt.BASESIZE=6;var St=kt,Mt={pt:1,mm:7227/2540,cm:7227/254,in:72.27,bp:1.00375,pc:12,dd:1238/1157,cc:14856/1157,nd:685/642,nc:1370/107,sp:1/65536,px:1.00375},zt={ex:!0,em:!0,mu:!0},At=function(t){return"string"!=typeof t&&(t=t.unit),t in Mt||t in zt||"ex"===t},Tt=function(t,e){var r;if(t.unit in Mt)r=Mt[t.unit]/e.fontMetrics().ptPerEm/e.sizeMultiplier;else if("mu"===t.unit)r=e.fontMetrics().cssEmPerMu;else{var n;if(n=e.style.isTight()?e.havingStyle(e.style.text()):e,"ex"===t.unit)r=n.fontMetrics().xHeight;else{if("em"!==t.unit)throw new o("Invalid unit: '"+t.unit+"'");r=n.fontMetrics().quad}n!==e&&(r*=n.sizeMultiplier/e.sizeMultiplier)}return Math.min(t.number*r,e.maxSize)},Bt=function(t,e,r){return X[r][t]&&X[r][t].replace&&(t=X[r][t].replace),{value:t,metrics:G(t,e,r)}},Ct=function(t,e,r,n,a){var i,o=Bt(t,e,r),s=o.metrics;if(t=o.value,s){var l=s.italic;("text"===r||n&&"mathit"===n.font)&&(l=0),i=new E(t,s.height,s.depth,l,s.skew,s.width,a)}else"undefined"!=typeof console&&console.warn("No character metrics for '"+t+"' in style '"+e+"' and mode '"+r+"'"),i=new E(t,0,0,0,0,0,a);if(n){i.maxFontSize=n.sizeMultiplier,n.style.isTight()&&i.classes.push("mtight");var h=n.getColor();h&&(i.style.color=h)}return i},qt=function(t,e){if(T(t.classes)!==T(e.classes)||t.skew!==e.skew||t.maxFontSize!==e.maxFontSize)return!1;for(var r in t.style)if(t.style.hasOwnProperty(r)&&t.style[r]!==e.style[r])return!1;for(var n in e.style)if(e.style.hasOwnProperty(n)&&t.style[n]!==e.style[n])return!1;return!0},Nt=function(t){for(var e=0,r=0,n=0,a=0;ae&&(e=i.height),i.depth>r&&(r=i.depth),i.maxFontSize>n&&(n=i.maxFontSize)}t.height=e,t.depth=r,t.maxFontSize=n},It=function(t,e,r,n){var a=new N(t,e,r,n);return Nt(a),a},Ot=function(t,e,r,n){return new N(t,e,r,n)},Rt=function(t){var e=new A(t);return Nt(e),e},Et=function(t,e,r){var n="";switch(t){case"amsrm":n="AMS";break;case"textrm":n="Main";break;case"textsf":n="SansSerif";break;case"texttt":n="Typewriter";break;default:n=t}return n+"-"+("textbf"===e&&"textit"===r?"BoldItalic":"textbf"===e?"Bold":"textit"===e?"Italic":"Regular")},Lt={mathbf:{variant:"bold",fontName:"Main-Bold"},mathrm:{variant:"normal",fontName:"Main-Regular"},textit:{variant:"italic",fontName:"Main-Italic"},mathit:{variant:"italic",fontName:"Main-Italic"},mathnormal:{variant:"italic",fontName:"Math-Italic"},mathbb:{variant:"double-struck",fontName:"AMS-Regular"},mathcal:{variant:"script",fontName:"Caligraphic-Regular"},mathfrak:{variant:"fraktur",fontName:"Fraktur-Regular"},mathscr:{variant:"script",fontName:"Script-Regular"},mathsf:{variant:"sans-serif",fontName:"SansSerif-Regular"},mathtt:{variant:"monospace",fontName:"Typewriter-Regular"}},Pt={vec:["vec",.471,.714],oiintSize1:["oiintSize1",.957,.499],oiintSize2:["oiintSize2",1.472,.659],oiiintSize1:["oiiintSize1",1.304,.499],oiiintSize2:["oiiintSize2",1.98,.659],leftParenInner:["leftParenInner",.875,.3],rightParenInner:["rightParenInner",.875,.3]},Dt={fontMap:Lt,makeSymbol:Ct,mathsym:function(t,e,r,n){return void 0===n&&(n=[]),"boldsymbol"===r.font&&Bt(t,"Main-Bold",e).metrics?Ct(t,"Main-Bold",e,r,n.concat(["mathbf"])):"\\"===t||"main"===X[e][t].font?Ct(t,"Main-Regular",e,r,n):Ct(t,"AMS-Regular",e,r,n.concat(["amsrm"]))},makeSpan:It,makeSvgSpan:Ot,makeLineSpan:function(t,e,r){var n=It([t],[],e);return n.height=Math.max(r||e.fontMetrics().defaultRuleThickness,e.minRuleThickness),n.style.borderBottomWidth=n.height+"em",n.maxFontSize=1,n},makeAnchor:function(t,e,r,n){var a=new I(t,e,r,n);return Nt(a),a},makeFragment:Rt,wrapFragment:function(t,e){return t instanceof A?It([],[t],e):t},makeVList:function(t,e){for(var r=function(t){if("individualShift"===t.positionType){for(var e=t.children,r=[e[0]],n=-e[0].shift-e[0].elem.depth,a=n,i=1;i0&&(i.push(se(o,e)),o=[]),i.push(a[s]));o.length>0&&i.push(se(o,e)),r&&((n=se(ee(r,e,!0))).classes=["tag"],i.push(n));var h=Zt(["katex-html"],i);if(h.setAttribute("aria-hidden","true"),n){var m=n.children[0];m.style.height=h.height+h.depth+"em",m.style.verticalAlign=-h.depth+"em"}return h}function he(t){return new A(t)}var me=function(){function t(t,e){this.type=void 0,this.attributes=void 0,this.children=void 0,this.type=t,this.attributes={},this.children=e||[]}var e=t.prototype;return e.setAttribute=function(t,e){this.attributes[t]=e},e.getAttribute=function(t){return this.attributes[t]},e.toNode=function(){var t=document.createElementNS("http://www.w3.org/1998/Math/MathML",this.type);for(var e in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,e)&&t.setAttribute(e,this.attributes[e]);for(var r=0;r"},e.toText=function(){return this.children.map((function(t){return t.toText()})).join("")},t}(),ce=function(){function t(t){this.text=void 0,this.text=t}var e=t.prototype;return e.toNode=function(){return document.createTextNode(this.text)},e.toMarkup=function(){return c.escape(this.toText())},e.toText=function(){return this.text},t}(),ue={MathNode:me,TextNode:ce,SpaceNode:function(){function t(t){this.width=void 0,this.character=void 0,this.width=t,this.character=t>=.05555&&t<=.05556?" ":t>=.1666&&t<=.1667?" ":t>=.2222&&t<=.2223?" ":t>=.2777&&t<=.2778?"  ":t>=-.05556&&t<=-.05555?" ⁣":t>=-.1667&&t<=-.1666?" ⁣":t>=-.2223&&t<=-.2222?" ⁣":t>=-.2778&&t<=-.2777?" ⁣":null}var e=t.prototype;return e.toNode=function(){if(this.character)return document.createTextNode(this.character);var t=document.createElementNS("http://www.w3.org/1998/Math/MathML","mspace");return t.setAttribute("width",this.width+"em"),t},e.toMarkup=function(){return this.character?""+this.character+"":''},e.toText=function(){return this.character?this.character:" "},t}(),newDocumentFragment:he},pe=function(t,e,r){return!X[e][t]||!X[e][t].replace||55349===t.charCodeAt(0)||rt.hasOwnProperty(t)&&r&&(r.fontFamily&&"tt"===r.fontFamily.substr(4,2)||r.font&&"tt"===r.font.substr(4,2))||(t=X[e][t].replace),new ue.TextNode(t)},de=function(t){return 1===t.length?t[0]:new ue.MathNode("mrow",t)},fe=function(t,e){if("texttt"===e.fontFamily)return"monospace";if("textsf"===e.fontFamily)return"textit"===e.fontShape&&"textbf"===e.fontWeight?"sans-serif-bold-italic":"textit"===e.fontShape?"sans-serif-italic":"textbf"===e.fontWeight?"bold-sans-serif":"sans-serif";if("textit"===e.fontShape&&"textbf"===e.fontWeight)return"bold-italic";if("textit"===e.fontShape)return"italic";if("textbf"===e.fontWeight)return"bold";var r=e.font;if(!r||"mathnormal"===r)return null;var n=t.mode;if("mathit"===r)return"italic";if("boldsymbol"===r)return"textord"===t.type?"bold":"bold-italic";if("mathbf"===r)return"bold";if("mathbb"===r)return"double-struck";if("mathfrak"===r)return"fraktur";if("mathscr"===r||"mathcal"===r)return"script";if("mathsf"===r)return"sans-serif";if("mathtt"===r)return"monospace";var a=t.text;return c.contains(["\\imath","\\jmath"],a)?null:(X[n][a]&&X[n][a].replace&&(a=X[n][a].replace),G(a,Dt.fontMap[r].fontName,n)?Dt.fontMap[r].variant:null)},ge=function(t,e,r){if(1===t.length){var n=ve(t[0],e);return r&&n instanceof me&&"mo"===n.type&&(n.setAttribute("lspace","0em"),n.setAttribute("rspace","0em")),[n]}for(var a,i=[],o=0;o0&&(p.text=p.text.slice(0,1)+"̸"+p.text.slice(1),i.pop())}}}i.push(s),a=s}return i},xe=function(t,e,r){return de(ge(t,e,r))},ve=function(t,e){if(!t)return new ue.MathNode("mrow");if($t[t.type])return $t[t.type](t,e);throw new o("Got group of unknown type: '"+t.type+"'")};function be(t,e,r,n,a){var i,o=ge(t,r);i=1===o.length&&o[0]instanceof me&&c.contains(["mrow","mtable"],o[0].type)?o[0]:new ue.MathNode("mrow",o);var s=new ue.MathNode("annotation",[new ue.TextNode(e)]);s.setAttribute("encoding","application/x-tex");var l=new ue.MathNode("semantics",[i,s]),h=new ue.MathNode("math",[l]);h.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),n&&h.setAttribute("display","block");var m=a?"katex":"katex-mathml";return Dt.makeSpan([m],[h])}var ye=function(t){return new St({style:t.displayMode?w.DISPLAY:w.TEXT,maxSize:t.maxSize,minRuleThickness:t.minRuleThickness})},we=function(t,e){if(e.displayMode){var r=["katex-display"];e.leqno&&r.push("leqno"),e.fleqn&&r.push("fleqn"),t=Dt.makeSpan(r,[t])}return t},ke=function(t,e,r){var n,a=ye(r);if("mathml"===r.output)return be(t,e,a,r.displayMode,!0);if("html"===r.output){var i=le(t,a);n=Dt.makeSpan(["katex"],[i])}else{var o=be(t,e,a,r.displayMode,!1),s=le(t,a);n=Dt.makeSpan(["katex"],[o,s])}return we(n,r)},Se={widehat:"^",widecheck:"ˇ",widetilde:"~",utilde:"~",overleftarrow:"←",underleftarrow:"←",xleftarrow:"←",overrightarrow:"→",underrightarrow:"→",xrightarrow:"→",underbrace:"⏟",overbrace:"⏞",overgroup:"⏠",undergroup:"⏡",overleftrightarrow:"↔",underleftrightarrow:"↔",xleftrightarrow:"↔",Overrightarrow:"⇒",xRightarrow:"⇒",overleftharpoon:"↼",xleftharpoonup:"↼",overrightharpoon:"⇀",xrightharpoonup:"⇀",xLeftarrow:"⇐",xLeftrightarrow:"⇔",xhookleftarrow:"↩",xhookrightarrow:"↪",xmapsto:"↦",xrightharpoondown:"⇁",xleftharpoondown:"↽",xrightleftharpoons:"⇌",xleftrightharpoons:"⇋",xtwoheadleftarrow:"↞",xtwoheadrightarrow:"↠",xlongequal:"=",xtofrom:"⇄",xrightleftarrows:"⇄",xrightequilibrium:"⇌",xleftequilibrium:"⇋"},Me={overrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],overleftarrow:[["leftarrow"],.888,522,"xMinYMin"],underrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],underleftarrow:[["leftarrow"],.888,522,"xMinYMin"],xrightarrow:[["rightarrow"],1.469,522,"xMaxYMin"],xleftarrow:[["leftarrow"],1.469,522,"xMinYMin"],Overrightarrow:[["doublerightarrow"],.888,560,"xMaxYMin"],xRightarrow:[["doublerightarrow"],1.526,560,"xMaxYMin"],xLeftarrow:[["doubleleftarrow"],1.526,560,"xMinYMin"],overleftharpoon:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoonup:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoondown:[["leftharpoondown"],.888,522,"xMinYMin"],overrightharpoon:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoonup:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoondown:[["rightharpoondown"],.888,522,"xMaxYMin"],xlongequal:[["longequal"],.888,334,"xMinYMin"],xtwoheadleftarrow:[["twoheadleftarrow"],.888,334,"xMinYMin"],xtwoheadrightarrow:[["twoheadrightarrow"],.888,334,"xMaxYMin"],overleftrightarrow:[["leftarrow","rightarrow"],.888,522],overbrace:[["leftbrace","midbrace","rightbrace"],1.6,548],underbrace:[["leftbraceunder","midbraceunder","rightbraceunder"],1.6,548],underleftrightarrow:[["leftarrow","rightarrow"],.888,522],xleftrightarrow:[["leftarrow","rightarrow"],1.75,522],xLeftrightarrow:[["doubleleftarrow","doublerightarrow"],1.75,560],xrightleftharpoons:[["leftharpoondownplus","rightharpoonplus"],1.75,716],xleftrightharpoons:[["leftharpoonplus","rightharpoondownplus"],1.75,716],xhookleftarrow:[["leftarrow","righthook"],1.08,522],xhookrightarrow:[["lefthook","rightarrow"],1.08,522],overlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],underlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],overgroup:[["leftgroup","rightgroup"],.888,342],undergroup:[["leftgroupunder","rightgroupunder"],.888,342],xmapsto:[["leftmapsto","rightarrow"],1.5,522],xtofrom:[["leftToFrom","rightToFrom"],1.75,528],xrightleftarrows:[["baraboveleftarrow","rightarrowabovebar"],1.75,901],xrightequilibrium:[["baraboveshortleftharpoon","rightharpoonaboveshortbar"],1.75,716],xleftequilibrium:[["shortbaraboveleftharpoon","shortrightharpoonabovebar"],1.75,716]},ze=function(t,e,r,n){var a,i=t.height+t.depth+2*r;if(/fbox|color/.test(e)){if(a=Dt.makeSpan(["stretchy",e],[],n),"fbox"===e){var o=n.color&&n.getColor();o&&(a.style.borderColor=o)}}else{var s=[];/^[bx]cancel$/.test(e)&&s.push(new D({x1:"0",y1:"0",x2:"100%",y2:"100%","stroke-width":"0.046em"})),/^x?cancel$/.test(e)&&s.push(new D({x1:"0",y1:"100%",x2:"100%",y2:"0","stroke-width":"0.046em"}));var l=new L(s,{width:"100%",height:i+"em"});a=Dt.makeSvgSpan([],[l],n)}return a.height=i,a.style.height=i+"em",a},Ae=function(t){var e=new ue.MathNode("mo",[new ue.TextNode(Se[t.substr(1)])]);return e.setAttribute("stretchy","true"),e},Te=function(t,e){var r=function(){var r=4e5,n=t.label.substr(1);if(c.contains(["widehat","widecheck","widetilde","utilde"],n)){var a,i,o,s="ordgroup"===(d=t.base).type?d.body.length:1;if(s>5)"widehat"===n||"widecheck"===n?(a=420,r=2364,o=.42,i=n+"4"):(a=312,r=2340,o=.34,i="tilde4");else{var l=[1,1,2,2,3,3][s];"widehat"===n||"widecheck"===n?(r=[0,1062,2364,2364,2364][l],a=[0,239,300,360,420][l],o=[0,.24,.3,.3,.36,.42][l],i=n+l):(r=[0,600,1033,2339,2340][l],a=[0,260,286,306,312][l],o=[0,.26,.286,.3,.306,.34][l],i="tilde"+l)}var h=new P(i),m=new L([h],{width:"100%",height:o+"em",viewBox:"0 0 "+r+" "+a,preserveAspectRatio:"none"});return{span:Dt.makeSvgSpan([],[m],e),minWidth:0,height:o}}var u,p,d,f=[],g=Me[n],x=g[0],v=g[1],b=g[2],y=b/1e3,w=x.length;if(1===w)u=["hide-tail"],p=[g[3]];else if(2===w)u=["halfarrow-left","halfarrow-right"],p=["xMinYMin","xMaxYMin"];else{if(3!==w)throw new Error("Correct katexImagesData or update code here to support\n "+w+" children.");u=["brace-left","brace-center","brace-right"],p=["xMinYMin","xMidYMin","xMaxYMin"]}for(var k=0;k0&&(n.style.minWidth=a+"em"),n};function Be(t,e){if(!t||t.type!==e)throw new Error("Expected node of type "+e+", but got "+(t?"node of type "+t.type:String(t)));return t}function Ce(t){var e=qe(t);if(!e)throw new Error("Expected node of symbol group type, but got "+(t?"node of type "+t.type:String(t)));return e}function qe(t){return t&&("atom"===t.type||$.hasOwnProperty(t.type))?t:null}var Ne=function(t,e){var r,n,a;t&&"supsub"===t.type?(r=(n=Be(t.base,"accent")).base,t.base=r,a=function(t){if(t instanceof N)return t;throw new Error("Expected span but got "+String(t)+".")}(oe(t,e)),t.base=n):r=(n=Be(t,"accent")).base;var i=oe(r,e.havingCrampedStyle()),o=0;if(n.isShifty&&c.isCharacterBox(r)){var s=c.getBaseElem(r);o=H(oe(s,e.havingCrampedStyle())).skew}var l,h=Math.min(i.height,e.fontMetrics().xHeight);if(n.isStretchy)l=Te(n,e),l=Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:i},{type:"elem",elem:l,wrapperClasses:["svg-align"],wrapperStyle:o>0?{width:"calc(100% - "+2*o+"em)",marginLeft:2*o+"em"}:void 0}]},e);else{var m,u;"\\vec"===n.label?(m=Dt.staticSvg("vec",e),u=Dt.svgData.vec[1]):((m=H(m=Dt.makeOrd({mode:n.mode,text:n.label},e,"textord"))).italic=0,u=m.width),l=Dt.makeSpan(["accent-body"],[m]);var p="\\textcircled"===n.label;p&&(l.classes.push("accent-full"),h=i.height);var d=o;p||(d-=u/2),l.style.left=d+"em","\\textcircled"===n.label&&(l.style.top=".2em"),l=Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:i},{type:"kern",size:-h},{type:"elem",elem:l}]},e)}var f=Dt.makeSpan(["mord","accent"],[l],e);return a?(a.children[0]=f,a.height=Math.max(f.height,a.height),a.classes[0]="mord",a):f},Ie=function(t,e){var r=t.isStretchy?Ae(t.label):new ue.MathNode("mo",[pe(t.label,t.mode)]),n=new ue.MathNode("mover",[ve(t.base,e),r]);return n.setAttribute("accent","true"),n},Oe=new RegExp(["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring"].map((function(t){return"\\"+t})).join("|"));Wt({type:"accent",names:["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon"],props:{numArgs:1},handler:function(t,e){var r=e[0],n=!Oe.test(t.funcName),a=!n||"\\widehat"===t.funcName||"\\widetilde"===t.funcName||"\\widecheck"===t.funcName;return{type:"accent",mode:t.parser.mode,label:t.funcName,isStretchy:n,isShifty:a,base:r}},htmlBuilder:Ne,mathmlBuilder:Ie}),Wt({type:"accent",names:["\\'","\\`","\\^","\\~","\\=","\\u","\\.",'\\"',"\\r","\\H","\\v","\\textcircled"],props:{numArgs:1,allowedInText:!0,allowedInMath:!1},handler:function(t,e){var r=e[0];return{type:"accent",mode:t.parser.mode,label:t.funcName,isStretchy:!1,isShifty:!0,base:r}},htmlBuilder:Ne,mathmlBuilder:Ie}),Wt({type:"accentUnder",names:["\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde"],props:{numArgs:1},handler:function(t,e){var r=t.parser,n=t.funcName,a=e[0];return{type:"accentUnder",mode:r.mode,label:n,base:a}},htmlBuilder:function(t,e){var r=oe(t.base,e),n=Te(t,e),a="\\utilde"===t.label?.12:0,i=Dt.makeVList({positionType:"top",positionData:r.height,children:[{type:"elem",elem:n,wrapperClasses:["svg-align"]},{type:"kern",size:a},{type:"elem",elem:r}]},e);return Dt.makeSpan(["mord","accentunder"],[i],e)},mathmlBuilder:function(t,e){var r=Ae(t.label),n=new ue.MathNode("munder",[ve(t.base,e),r]);return n.setAttribute("accentunder","true"),n}});var Re=function(t){var e=new ue.MathNode("mpadded",t?[t]:[]);return e.setAttribute("width","+0.6em"),e.setAttribute("lspace","0.3em"),e};Wt({type:"xArrow",names:["\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium"],props:{numArgs:1,numOptionalArgs:1},handler:function(t,e,r){var n=t.parser,a=t.funcName;return{type:"xArrow",mode:n.mode,label:a,body:e[0],below:r[0]}},htmlBuilder:function(t,e){var r,n=e.style,a=e.havingStyle(n.sup()),i=Dt.wrapFragment(oe(t.body,a,e),e);i.classes.push("x-arrow-pad"),t.below&&(a=e.havingStyle(n.sub()),(r=Dt.wrapFragment(oe(t.below,a,e),e)).classes.push("x-arrow-pad"));var o,s=Te(t,e),l=-e.fontMetrics().axisHeight+.5*s.height,h=-e.fontMetrics().axisHeight-.5*s.height-.111;if((i.depth>.25||"\\xleftequilibrium"===t.label)&&(h-=i.depth),r){var m=-e.fontMetrics().axisHeight+r.height+.5*s.height+.111;o=Dt.makeVList({positionType:"individualShift",children:[{type:"elem",elem:i,shift:h},{type:"elem",elem:s,shift:l},{type:"elem",elem:r,shift:m}]},e)}else o=Dt.makeVList({positionType:"individualShift",children:[{type:"elem",elem:i,shift:h},{type:"elem",elem:s,shift:l}]},e);return o.children[0].children[0].children[1].classes.push("svg-align"),Dt.makeSpan(["mrel","x-arrow"],[o],e)},mathmlBuilder:function(t,e){var r,n=Ae(t.label);if(t.body){var a=Re(ve(t.body,e));if(t.below){var i=Re(ve(t.below,e));r=new ue.MathNode("munderover",[n,i,a])}else r=new ue.MathNode("mover",[n,a])}else if(t.below){var o=Re(ve(t.below,e));r=new ue.MathNode("munder",[n,o])}else r=Re(),r=new ue.MathNode("mover",[n,r]);return r}}),Wt({type:"textord",names:["\\@char"],props:{numArgs:1,allowedInText:!0},handler:function(t,e){for(var r=t.parser,n=Be(e[0],"ordgroup").body,a="",i=0;i","\\langle","\\rangle","/","\\backslash","\\lt","\\gt"],Ke=[0,1.2,1.8,2.4,3],Je=[{type:"small",style:w.SCRIPTSCRIPT},{type:"small",style:w.SCRIPT},{type:"small",style:w.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4}],Qe=[{type:"small",style:w.SCRIPTSCRIPT},{type:"small",style:w.SCRIPT},{type:"small",style:w.TEXT},{type:"stack"}],tr=[{type:"small",style:w.SCRIPTSCRIPT},{type:"small",style:w.SCRIPT},{type:"small",style:w.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4},{type:"stack"}],er=function(t){if("small"===t.type)return"Main-Regular";if("large"===t.type)return"Size"+t.size+"-Regular";if("stack"===t.type)return"Size4-Regular";throw new Error("Add support for delim type '"+t.type+"' here.")},rr=function(t,e,r,n){for(var a=Math.min(2,3-n.style.size);ae)return r[a]}return r[r.length-1]},nr=function(t,e,r,n,a,i){var o;"<"===t||"\\lt"===t||"⟨"===t?t="\\langle":">"!==t&&"\\gt"!==t&&"⟩"!==t||(t="\\rangle"),o=c.contains(Ze,t)?Je:c.contains(Xe,t)?tr:Qe;var s=rr(t,e,o,n);return"small"===s.type?function(t,e,r,n,a,i){var o=Dt.makeSymbol(t,"Main-Regular",a,n),s=Ve(o,e,n,i);return r&&Ue(s,n,e),s}(t,s.style,r,n,a,i):"large"===s.type?Ge(t,s.size,r,n,a,i):$e(t,e,r,n,a,i)},ar=function(t,e){var r,n,a=e.havingBaseSizing(),i=rr("\\surd",t*a.sizeMultiplier,tr,a),o=a.sizeMultiplier,s=Math.max(0,e.minRuleThickness-e.fontMetrics().sqrtRuleThickness),l=0,h=0,m=0;return"small"===i.type?(t<1?o=1:t<1.4&&(o=.7),h=(1+s)/o,(r=We("sqrtMain",l=(1+s+.08)/o,m=1e3+1e3*s+80,s,e)).style.minWidth="0.853em",n=.833/o):"large"===i.type?(m=1080*Ke[i.size],h=(Ke[i.size]+s)/o,l=(Ke[i.size]+s+.08)/o,(r=We("sqrtSize"+i.size,l,m,s,e)).style.minWidth="1.02em",n=1/o):(l=t+s+.08,h=t+s,m=Math.floor(1e3*t+s)+80,(r=We("sqrtTall",l,m,s,e)).style.minWidth="0.742em",n=1.056),r.height=h,r.style.height=l+"em",{span:r,advanceWidth:n,ruleWidth:(e.fontMetrics().sqrtRuleThickness+s)*o}},ir=function(t,e,r,n,a){if("<"===t||"\\lt"===t||"⟨"===t?t="\\langle":">"!==t&&"\\gt"!==t&&"⟩"!==t||(t="\\rangle"),c.contains(Xe,t)||c.contains(Ze,t))return Ge(t,e,!1,r,n,a);if(c.contains(je,t))return $e(t,Ke[e],!1,r,n,a);throw new o("Illegal delimiter: '"+t+"'")},or=nr,sr=function(t,e,r,n,a,i){var o=n.fontMetrics().axisHeight*n.sizeMultiplier,s=5/n.fontMetrics().ptPerEm,l=Math.max(e-o,r+o),h=Math.max(l/500*901,2*l-s);return nr(t,h,!0,n,a,i)},lr={"\\bigl":{mclass:"mopen",size:1},"\\Bigl":{mclass:"mopen",size:2},"\\biggl":{mclass:"mopen",size:3},"\\Biggl":{mclass:"mopen",size:4},"\\bigr":{mclass:"mclose",size:1},"\\Bigr":{mclass:"mclose",size:2},"\\biggr":{mclass:"mclose",size:3},"\\Biggr":{mclass:"mclose",size:4},"\\bigm":{mclass:"mrel",size:1},"\\Bigm":{mclass:"mrel",size:2},"\\biggm":{mclass:"mrel",size:3},"\\Biggm":{mclass:"mrel",size:4},"\\big":{mclass:"mord",size:1},"\\Big":{mclass:"mord",size:2},"\\bigg":{mclass:"mord",size:3},"\\Bigg":{mclass:"mord",size:4}},hr=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","⌊","⌋","\\lceil","\\rceil","⌈","⌉","<",">","\\langle","⟨","\\rangle","⟩","\\lt","\\gt","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","⟮","⟯","\\lmoustache","\\rmoustache","⎰","⎱","/","\\backslash","|","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","."];function mr(t,e){var r=qe(t);if(r&&c.contains(hr,r.text))return r;throw new o(r?"Invalid delimiter '"+r.text+"' after '"+e.funcName+"'":"Invalid delimiter type '"+t.type+"'",t)}function cr(t){if(!t.body)throw new Error("Bug: The leftright ParseNode wasn't fully parsed.")}Wt({type:"delimsizing",names:["\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg"],props:{numArgs:1},handler:function(t,e){var r=mr(e[0],t);return{type:"delimsizing",mode:t.parser.mode,size:lr[t.funcName].size,mclass:lr[t.funcName].mclass,delim:r.text}},htmlBuilder:function(t,e){return"."===t.delim?Dt.makeSpan([t.mclass]):ir(t.delim,t.size,e,t.mode,[t.mclass])},mathmlBuilder:function(t){var e=[];"."!==t.delim&&e.push(pe(t.delim,t.mode));var r=new ue.MathNode("mo",e);return"mopen"===t.mclass||"mclose"===t.mclass?r.setAttribute("fence","true"):r.setAttribute("fence","false"),r}}),Wt({type:"leftright-right",names:["\\right"],props:{numArgs:1},handler:function(t,e){var r=t.parser.gullet.macros.get("\\current@color");if(r&&"string"!=typeof r)throw new o("\\current@color set to non-string in \\right");return{type:"leftright-right",mode:t.parser.mode,delim:mr(e[0],t).text,color:r}}}),Wt({type:"leftright",names:["\\left"],props:{numArgs:1},handler:function(t,e){var r=mr(e[0],t),n=t.parser;++n.leftrightDepth;var a=n.parseExpression(!1);--n.leftrightDepth,n.expect("\\right",!1);var i=Be(n.parseFunction(),"leftright-right");return{type:"leftright",mode:n.mode,body:a,left:r.text,right:i.delim,rightColor:i.color}},htmlBuilder:function(t,e){cr(t);for(var r,n,a=ee(t.body,e,!0,["mopen","mclose"]),i=0,o=0,s=!1,l=0;l-1?"mpadded":"menclose",[ve(t.body,e)]);switch(t.label){case"\\cancel":n.setAttribute("notation","updiagonalstrike");break;case"\\bcancel":n.setAttribute("notation","downdiagonalstrike");break;case"\\sout":n.setAttribute("notation","horizontalstrike");break;case"\\fbox":n.setAttribute("notation","box");break;case"\\fcolorbox":case"\\colorbox":if(r=e.fontMetrics().fboxsep*e.fontMetrics().ptPerEm,n.setAttribute("width","+"+2*r+"pt"),n.setAttribute("height","+"+2*r+"pt"),n.setAttribute("lspace",r+"pt"),n.setAttribute("voffset",r+"pt"),"\\fcolorbox"===t.label){var a=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness);n.setAttribute("style","border: "+a+"em solid "+String(t.borderColor))}break;case"\\xcancel":n.setAttribute("notation","updiagonalstrike downdiagonalstrike")}return t.backgroundColor&&n.setAttribute("mathbackground",t.backgroundColor),n};Wt({type:"enclose",names:["\\colorbox"],props:{numArgs:2,allowedInText:!0,greediness:3,argTypes:["color","text"]},handler:function(t,e,r){var n=t.parser,a=t.funcName,i=Be(e[0],"color-token").color,o=e[1];return{type:"enclose",mode:n.mode,label:a,backgroundColor:i,body:o}},htmlBuilder:ur,mathmlBuilder:pr}),Wt({type:"enclose",names:["\\fcolorbox"],props:{numArgs:3,allowedInText:!0,greediness:3,argTypes:["color","color","text"]},handler:function(t,e,r){var n=t.parser,a=t.funcName,i=Be(e[0],"color-token").color,o=Be(e[1],"color-token").color,s=e[2];return{type:"enclose",mode:n.mode,label:a,backgroundColor:o,borderColor:i,body:s}},htmlBuilder:ur,mathmlBuilder:pr}),Wt({type:"enclose",names:["\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler:function(t,e){return{type:"enclose",mode:t.parser.mode,label:"\\fbox",body:e[0]}}}),Wt({type:"enclose",names:["\\cancel","\\bcancel","\\xcancel","\\sout"],props:{numArgs:1},handler:function(t,e,r){var n=t.parser,a=t.funcName,i=e[0];return{type:"enclose",mode:n.mode,label:a,body:i}},htmlBuilder:ur,mathmlBuilder:pr});var dr={};function fr(t){for(var e=t.type,r=t.names,n=t.props,a=t.handler,i=t.htmlBuilder,o=t.mathmlBuilder,s={type:e,numArgs:n.numArgs||0,greediness:1,allowedInText:!1,numOptionalArgs:0,handler:a},l=0;l0&&(b+=.25),h.push({pos:b,isDashed:t[e]})}for(y(i[0]),r=0;r0&&(M<(B+=v)&&(M=B),B=0),t.addJot&&(M+=f),z.height=S,z.depth=M,b+=S,z.pos=b,b+=M+B,l[r]=z,y(i[r+1])}var C,q,N=b/2+e.fontMetrics().axisHeight,I=t.cols||[],O=[];for(n=0,q=0;n=s)){var D=void 0;(n>0||t.hskipBeforeAndAfter)&&0!==(D=c.deflt(R.pregap,p))&&((C=Dt.makeSpan(["arraycolsep"],[])).style.width=D+"em",O.push(C));var H=[];for(r=0;r0){for(var G=Dt.makeLineSpan("hline",e,m),_=Dt.makeLineSpan("hdashline",e,m),Y=[{type:"elem",elem:l,shift:0}];h.length>0;){var $=h.pop(),W=$.pos-N;$.isDashed?Y.push({type:"elem",elem:_,shift:W}):Y.push({type:"elem",elem:G,shift:W})}l=Dt.makeVList({positionType:"individualShift",children:Y},e)}return Dt.makeSpan(["mord"],[l],e)},yr={c:"center ",l:"left ",r:"right "},wr=function(t,e){var r=new ue.MathNode("mtable",t.body.map((function(t){return new ue.MathNode("mtr",t.map((function(t){return new ue.MathNode("mtd",[ve(t,e)])})))}))),n=.5===t.arraystretch?.1:.16+t.arraystretch-1+(t.addJot?.09:0);r.setAttribute("rowspacing",n+"em");var a="",i="";if(t.cols&&t.cols.length>0){var o=t.cols,s="",l=!1,h=0,m=o.length;"separator"===o[0].type&&(a+="top ",h=1),"separator"===o[o.length-1].type&&(a+="bottom ",m-=1);for(var c=h;c0?"left ":"",a+=g[g.length-1].length>0?"right ":"";for(var x=1;x0&&m&&(p=1),n[c]={type:"align",align:u,pregap:p,postgap:0}}return a.colSeparationType=m?"align":"alignat",a};fr({type:"array",names:["array","darray"],props:{numArgs:1},handler:function(t,e){var r={cols:(qe(e[0])?[e[0]]:Be(e[0],"ordgroup").body).map((function(t){var e=Ce(t).text;if(-1!=="lcr".indexOf(e))return{type:"align",align:e};if("|"===e)return{type:"separator",separator:"|"};if(":"===e)return{type:"separator",separator:":"};throw new o("Unknown column alignment: "+e,t)})),hskipBeforeAndAfter:!0};return xr(t.parser,r,vr(t.envName))},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix"],props:{numArgs:0},handler:function(t){var e={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[t.envName],r=xr(t.parser,{hskipBeforeAndAfter:!1},vr(t.envName));return e?{type:"leftright",mode:t.mode,body:[r],left:e[0],right:e[1],rightColor:void 0}:r},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["smallmatrix"],props:{numArgs:0},handler:function(t){var e=xr(t.parser,{arraystretch:.5},"script");return e.colSeparationType="small",e},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["subarray"],props:{numArgs:1},handler:function(t,e){var r=(qe(e[0])?[e[0]]:Be(e[0],"ordgroup").body).map((function(t){var e=Ce(t).text;if(-1!=="lc".indexOf(e))return{type:"align",align:e};throw new o("Unknown column alignment: "+e,t)}));if(r.length>1)throw new o("{subarray} can contain only one column");var n={cols:r,hskipBeforeAndAfter:!1,arraystretch:.5};if((n=xr(t.parser,n,"script")).body.length>0&&n.body[0].length>1)throw new o("{subarray} can contain only one column");return n},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler:function(t){var e=xr(t.parser,{arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},vr(t.envName));return{type:"leftright",mode:t.mode,body:[e],left:t.envName.indexOf("r")>-1?".":"\\{",right:t.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["aligned"],props:{numArgs:0},handler:kr,htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["gathered"],props:{numArgs:0},handler:function(t){return xr(t.parser,{cols:[{type:"align",align:"c"}],addJot:!0},"display")},htmlBuilder:br,mathmlBuilder:wr}),fr({type:"array",names:["alignedat"],props:{numArgs:1},handler:kr,htmlBuilder:br,mathmlBuilder:wr}),Wt({type:"text",names:["\\hline","\\hdashline"],props:{numArgs:0,allowedInText:!0,allowedInMath:!0},handler:function(t,e){throw new o(t.funcName+" valid only within array environment")}});var Sr=dr;Wt({type:"environment",names:["\\begin","\\end"],props:{numArgs:1,argTypes:["text"]},handler:function(t,e){var r=t.parser,n=t.funcName,a=e[0];if("ordgroup"!==a.type)throw new o("Invalid environment name",a);for(var i="",s=0;s=w.SCRIPT.id?r.text():w.DISPLAY:"text"===t&&r.size===w.DISPLAY.size?r=w.TEXT:"script"===t?r=w.SCRIPT:"scriptscript"===t&&(r=w.SCRIPTSCRIPT),r},Ir=function(t,e){var r,n=Nr(t.size,e.style),a=n.fracNum(),i=n.fracDen();r=e.havingStyle(a);var o=oe(t.numer,r,e);if(t.continued){var s=8.5/e.fontMetrics().ptPerEm,l=3.5/e.fontMetrics().ptPerEm;o.height=o.height0?3*c:7*c,d=e.fontMetrics().denom1):(m>0?(u=e.fontMetrics().num2,p=c):(u=e.fontMetrics().num3,p=3*c),d=e.fontMetrics().denom2),h){var y=e.fontMetrics().axisHeight;u-o.depth-(y+.5*m)0&&(e="."===(e=t)?null:e),e};Wt({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,greediness:6,argTypes:["math","math","size","text","math","math"]},handler:function(t,e){var r,n=t.parser,a=e[4],i=e[5],o="atom"===e[0].type&&"open"===e[0].family?Er(e[0].text):null,s="atom"===e[1].type&&"close"===e[1].family?Er(e[1].text):null,l=Be(e[2],"size"),h=null;r=!!l.isBlank||(h=l.value).number>0;var m="auto",c=e[3];if("ordgroup"===c.type){if(c.body.length>0){var u=Be(c.body[0],"textord");m=Rr[Number(u.text)]}}else c=Be(c,"textord"),m=Rr[Number(c.text)];return{type:"genfrac",mode:n.mode,numer:a,denom:i,continued:!1,hasBarLine:r,barSize:h,leftDelim:o,rightDelim:s,size:m}},htmlBuilder:Ir,mathmlBuilder:Or}),Wt({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler:function(t,e){var r=t.parser,n=(t.funcName,t.token);return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:Be(e[0],"size").value,token:n}}}),Wt({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:function(t,e){var r=t.parser,n=(t.funcName,e[0]),a=function(t){if(!t)throw new Error("Expected non-null, but got "+String(t));return t}(Be(e[1],"infix").size),i=e[2],o=a.number>0;return{type:"genfrac",mode:r.mode,numer:n,denom:i,continued:!1,hasBarLine:o,barSize:a,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:Ir,mathmlBuilder:Or});var Lr=function(t,e){var r,n,a=e.style;"supsub"===t.type?(r=t.sup?oe(t.sup,e.havingStyle(a.sup()),e):oe(t.sub,e.havingStyle(a.sub()),e),n=Be(t.base,"horizBrace")):n=Be(t,"horizBrace");var i,o=oe(n.base,e.havingBaseStyle(w.DISPLAY)),s=Te(n,e);if(n.isOver?(i=Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:o},{type:"kern",size:.1},{type:"elem",elem:s}]},e)).children[0].children[0].children[1].classes.push("svg-align"):(i=Dt.makeVList({positionType:"bottom",positionData:o.depth+.1+s.height,children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:o}]},e)).children[0].children[0].children[0].classes.push("svg-align"),r){var l=Dt.makeSpan(["mord",n.isOver?"mover":"munder"],[i],e);i=n.isOver?Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:l},{type:"kern",size:.2},{type:"elem",elem:r}]},e):Dt.makeVList({positionType:"bottom",positionData:l.depth+.2+r.height+r.depth,children:[{type:"elem",elem:r},{type:"kern",size:.2},{type:"elem",elem:l}]},e)}return Dt.makeSpan(["mord",n.isOver?"mover":"munder"],[i],e)};Wt({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler:function(t,e){var r=t.parser,n=t.funcName;return{type:"horizBrace",mode:r.mode,label:n,isOver:/^\\over/.test(n),base:e[0]}},htmlBuilder:Lr,mathmlBuilder:function(t,e){var r=Ae(t.label);return new ue.MathNode(t.isOver?"mover":"munder",[ve(t.base,e),r])}}),Wt({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:function(t,e){var r=t.parser,n=e[1],a=Be(e[0],"url").url;return r.settings.isTrusted({command:"\\href",url:a})?{type:"href",mode:r.mode,href:a,body:jt(n)}:r.formatUnsupportedCmd("\\href")},htmlBuilder:function(t,e){var r=ee(t.body,e,!1);return Dt.makeAnchor(t.href,[],r,e)},mathmlBuilder:function(t,e){var r=xe(t.body,e);return r instanceof me||(r=new me("mrow",[r])),r.setAttribute("href",t.href),r}}),Wt({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:function(t,e){var r=t.parser,n=Be(e[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:n}))return r.formatUnsupportedCmd("\\url");for(var a=[],i=0;i0&&(n=Tt(t.totalheight,e)-r,n=Number(n.toFixed(2)));var a=0;t.width.number>0&&(a=Tt(t.width,e));var i={height:r+n+"em"};a>0&&(i.width=a+"em"),n>0&&(i.verticalAlign=-n+"em");var o=new O(t.src,t.alt,i);return o.height=r,o.depth=n,o},mathmlBuilder:function(t,e){var r=new ue.MathNode("mglyph",[]);r.setAttribute("alt",t.alt);var n=Tt(t.height,e),a=0;if(t.totalheight.number>0&&(a=(a=Tt(t.totalheight,e)-n).toFixed(2),r.setAttribute("valign","-"+a+"em")),r.setAttribute("height",n+a+"em"),t.width.number>0){var i=Tt(t.width,e);r.setAttribute("width",i+"em")}return r.setAttribute("src",t.src),r}}),Wt({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],allowedInText:!0},handler:function(t,e){var r=t.parser,n=t.funcName,a=Be(e[0],"size");if(r.settings.strict){var i="m"===n[1],o="mu"===a.value.unit;i?(o||r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" supports only mu units, not "+a.value.unit+" units"),"math"!==r.mode&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" works only in math mode")):o&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" doesn't support mu units")}return{type:"kern",mode:r.mode,dimension:a.value}},htmlBuilder:function(t,e){return Dt.makeGlue(t.dimension,e)},mathmlBuilder:function(t,e){var r=Tt(t.dimension,e);return new ue.SpaceNode(r)}}),Wt({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:function(t,e){var r=t.parser,n=t.funcName,a=e[0];return{type:"lap",mode:r.mode,alignment:n.slice(5),body:a}},htmlBuilder:function(t,e){var r;"clap"===t.alignment?(r=Dt.makeSpan([],[oe(t.body,e)]),r=Dt.makeSpan(["inner"],[r],e)):r=Dt.makeSpan(["inner"],[oe(t.body,e)]);var n=Dt.makeSpan(["fix"],[]),a=Dt.makeSpan([t.alignment],[r,n],e),i=Dt.makeSpan(["strut"]);return i.style.height=a.height+a.depth+"em",i.style.verticalAlign=-a.depth+"em",a.children.unshift(i),a=Dt.makeSpan(["thinbox"],[a],e),Dt.makeSpan(["mord","vbox"],[a],e)},mathmlBuilder:function(t,e){var r=new ue.MathNode("mpadded",[ve(t.body,e)]);if("rlap"!==t.alignment){var n="llap"===t.alignment?"-1":"-0.5";r.setAttribute("lspace",n+"width")}return r.setAttribute("width","0px"),r}}),Wt({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(t,e){var r=t.funcName,n=t.parser,a=n.mode;n.switchMode("math");var i="\\("===r?"\\)":"$",o=n.parseExpression(!1,i);return n.expect(i),n.switchMode(a),{type:"styling",mode:n.mode,style:"text",body:o}}}),Wt({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler:function(t,e){throw new o("Mismatched "+t.funcName)}});var Dr=function(t,e){switch(e.style.size){case w.DISPLAY.size:return t.display;case w.TEXT.size:return t.text;case w.SCRIPT.size:return t.script;case w.SCRIPTSCRIPT.size:return t.scriptscript;default:return t.text}};Wt({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4},handler:function(t,e){return{type:"mathchoice",mode:t.parser.mode,display:jt(e[0]),text:jt(e[1]),script:jt(e[2]),scriptscript:jt(e[3])}},htmlBuilder:function(t,e){var r=Dr(t,e),n=ee(r,e,!1);return Dt.makeFragment(n)},mathmlBuilder:function(t,e){var r=Dr(t,e);return xe(r,e)}});var Hr=function(t,e,r,n,a,i,o){var s,l,h;if(t=Dt.makeSpan([],[t]),e){var m=oe(e,n.havingStyle(a.sup()),n);l={elem:m,kern:Math.max(n.fontMetrics().bigOpSpacing1,n.fontMetrics().bigOpSpacing3-m.depth)}}if(r){var c=oe(r,n.havingStyle(a.sub()),n);s={elem:c,kern:Math.max(n.fontMetrics().bigOpSpacing2,n.fontMetrics().bigOpSpacing4-c.height)}}if(l&&s){var u=n.fontMetrics().bigOpSpacing5+s.elem.height+s.elem.depth+s.kern+t.depth+o;h=Dt.makeVList({positionType:"bottom",positionData:u,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:-i+"em"},{type:"kern",size:s.kern},{type:"elem",elem:t},{type:"kern",size:l.kern},{type:"elem",elem:l.elem,marginLeft:i+"em"},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else if(s){var p=t.height-o;h=Dt.makeVList({positionType:"top",positionData:p,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:s.elem,marginLeft:-i+"em"},{type:"kern",size:s.kern},{type:"elem",elem:t}]},n)}else{if(!l)return t;var d=t.depth+o;h=Dt.makeVList({positionType:"bottom",positionData:d,children:[{type:"elem",elem:t},{type:"kern",size:l.kern},{type:"elem",elem:l.elem,marginLeft:i+"em"},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}return Dt.makeSpan(["mop","op-limits"],[h],n)},Fr=["\\smallint"],Vr=function(t,e){var r,n,a,i=!1;"supsub"===t.type?(r=t.sup,n=t.sub,a=Be(t.base,"op"),i=!0):a=Be(t,"op");var o,s=e.style,l=!1;if(s.size===w.DISPLAY.size&&a.symbol&&!c.contains(Fr,a.name)&&(l=!0),a.symbol){var h=l?"Size2-Regular":"Size1-Regular",m="";if("\\oiint"!==a.name&&"\\oiiint"!==a.name||(m=a.name.substr(1),a.name="oiint"===m?"\\iint":"\\iiint"),o=Dt.makeSymbol(a.name,h,"math",e,["mop","op-symbol",l?"large-op":"small-op"]),m.length>0){var u=o.italic,p=Dt.staticSvg(m+"Size"+(l?"2":"1"),e);o=Dt.makeVList({positionType:"individualShift",children:[{type:"elem",elem:o,shift:0},{type:"elem",elem:p,shift:l?.08:0}]},e),a.name="\\"+m,o.classes.unshift("mop"),o.italic=u}}else if(a.body){var d=ee(a.body,e,!0);1===d.length&&d[0]instanceof E?(o=d[0]).classes[0]="mop":o=Dt.makeSpan(["mop"],Dt.tryCombineChars(d),e)}else{for(var f=[],g=1;g0){for(var s=a.body.map((function(t){var e=t.text;return"string"==typeof e?{type:"textord",mode:t.mode,text:e}:t})),l=ee(s,e.withFont("mathrm"),!0),h=0;h=0?s.setAttribute("height","+"+a+"em"):(s.setAttribute("height",a+"em"),s.setAttribute("depth","+"+-a+"em")),s.setAttribute("voffset",a+"em"),s}});var Wr=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"];Wt({type:"sizing",names:Wr,props:{numArgs:0,allowedInText:!0},handler:function(t,e){var r=t.breakOnTokenText,n=t.funcName,a=t.parser,i=a.parseExpression(!1,r);return{type:"sizing",mode:a.mode,size:Wr.indexOf(n)+1,body:i}},htmlBuilder:function(t,e){var r=e.havingSize(t.size);return $r(t.body,r,e)},mathmlBuilder:function(t,e){var r=e.havingSize(t.size),n=ge(t.body,r),a=new ue.MathNode("mstyle",n);return a.setAttribute("mathsize",r.sizeMultiplier+"em"),a}}),Wt({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:function(t,e,r){var n=t.parser,a=!1,i=!1,o=r[0]&&Be(r[0],"ordgroup");if(o)for(var s="",l=0;lr.height+r.depth+i&&(i=(i+c-r.height-r.depth)/2);var u=l.height-r.height-i-h;r.style.paddingLeft=m+"em";var p=Dt.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:-(r.height+u)},{type:"elem",elem:l},{type:"kern",size:h}]},e);if(t.index){var d=e.havingStyle(w.SCRIPTSCRIPT),f=oe(t.index,d,e),g=.6*(p.height-p.depth),x=Dt.makeVList({positionType:"shift",positionData:-g,children:[{type:"elem",elem:f}]},e),v=Dt.makeSpan(["root"],[x]);return Dt.makeSpan(["mord","sqrt"],[v,p],e)}return Dt.makeSpan(["mord","sqrt"],[p],e)},mathmlBuilder:function(t,e){var r=t.body,n=t.index;return n?new ue.MathNode("mroot",[ve(r,e),ve(n,e)]):new ue.MathNode("msqrt",[ve(r,e)])}});var Xr={display:w.DISPLAY,text:w.TEXT,script:w.SCRIPT,scriptscript:w.SCRIPTSCRIPT};Wt({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0},handler:function(t,e){var r=t.breakOnTokenText,n=t.funcName,a=t.parser,i=a.parseExpression(!0,r),o=n.slice(1,n.length-5);return{type:"styling",mode:a.mode,style:o,body:i}},htmlBuilder:function(t,e){var r=Xr[t.style],n=e.havingStyle(r).withFont("");return $r(t.body,n,e)},mathmlBuilder:function(t,e){var r=Xr[t.style],n=e.havingStyle(r),a=ge(t.body,n),i=new ue.MathNode("mstyle",a),o={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]}[t.style];return i.setAttribute("scriptlevel",o[0]),i.setAttribute("displaystyle",o[1]),i}}),Xt({type:"supsub",htmlBuilder:function(t,e){var r=function(t,e){var r=t.base;return r?"op"===r.type?r.limits&&(e.style.size===w.DISPLAY.size||r.alwaysHandleSupSub)?Vr:null:"operatorname"===r.type?r.alwaysHandleSupSub&&(e.style.size===w.DISPLAY.size||r.limits)?Yr:null:"accent"===r.type?c.isCharacterBox(r.base)?Ne:null:"horizBrace"===r.type&&!t.sub===r.isOver?Lr:null:null}(t,e);if(r)return r(t,e);var n,a,i,o=t.base,s=t.sup,l=t.sub,h=oe(o,e),m=e.fontMetrics(),u=0,p=0,d=o&&c.isCharacterBox(o);if(s){var f=e.havingStyle(e.style.sup());n=oe(s,f,e),d||(u=h.height-f.fontMetrics().supDrop*f.sizeMultiplier/e.sizeMultiplier)}if(l){var g=e.havingStyle(e.style.sub());a=oe(l,g,e),d||(p=h.depth+g.fontMetrics().subDrop*g.sizeMultiplier/e.sizeMultiplier)}i=e.style===w.DISPLAY?m.sup1:e.style.cramped?m.sup3:m.sup2;var x,v=e.sizeMultiplier,b=.5/m.ptPerEm/v+"em",y=null;if(a){var k=t.base&&"op"===t.base.type&&t.base.name&&("\\oiint"===t.base.name||"\\oiiint"===t.base.name);(h instanceof E||k)&&(y=-h.italic+"em")}if(n&&a){u=Math.max(u,i,n.depth+.25*m.xHeight),p=Math.max(p,m.sub2);var S=4*m.defaultRuleThickness;if(u-n.depth-(a.height-p)0&&(u+=M,p-=M)}var z=[{type:"elem",elem:a,shift:p,marginRight:b,marginLeft:y},{type:"elem",elem:n,shift:-u,marginRight:b}];x=Dt.makeVList({positionType:"individualShift",children:z},e)}else if(a){p=Math.max(p,m.sub1,a.height-.8*m.xHeight);var A=[{type:"elem",elem:a,marginLeft:y,marginRight:b}];x=Dt.makeVList({positionType:"shift",positionData:p,children:A},e)}else{if(!n)throw new Error("supsub must have either sup or sub.");u=Math.max(u,i,n.depth+.25*m.xHeight),x=Dt.makeVList({positionType:"shift",positionData:-u,children:[{type:"elem",elem:n,marginRight:b}]},e)}var T=ae(h,"right")||"mord";return Dt.makeSpan([T],[h,Dt.makeSpan(["msupsub"],[x])],e)},mathmlBuilder:function(t,e){var r,n=!1;t.base&&"horizBrace"===t.base.type&&!!t.sup===t.base.isOver&&(n=!0,r=t.base.isOver),!t.base||"op"!==t.base.type&&"operatorname"!==t.base.type||(t.base.parentIsSupSub=!0);var a,i=[ve(t.base,e)];if(t.sub&&i.push(ve(t.sub,e)),t.sup&&i.push(ve(t.sup,e)),n)a=r?"mover":"munder";else if(t.sub)if(t.sup){var o=t.base;a=o&&"op"===o.type&&o.limits&&e.style===w.DISPLAY||o&&"operatorname"===o.type&&o.alwaysHandleSupSub&&(e.style===w.DISPLAY||o.limits)?"munderover":"msubsup"}else{var s=t.base;a=s&&"op"===s.type&&s.limits&&(e.style===w.DISPLAY||s.alwaysHandleSupSub)||s&&"operatorname"===s.type&&s.alwaysHandleSupSub&&(s.limits||e.style===w.DISPLAY)?"munder":"msub"}else{var l=t.base;a=l&&"op"===l.type&&l.limits&&(e.style===w.DISPLAY||l.alwaysHandleSupSub)||l&&"operatorname"===l.type&&l.alwaysHandleSupSub&&(l.limits||e.style===w.DISPLAY)?"mover":"msup"}return new ue.MathNode(a,i)}}),Xt({type:"atom",htmlBuilder:function(t,e){return Dt.mathsym(t.text,t.mode,e,["m"+t.family])},mathmlBuilder:function(t,e){var r=new ue.MathNode("mo",[pe(t.text,t.mode)]);if("bin"===t.family){var n=fe(t,e);"bold-italic"===n&&r.setAttribute("mathvariant",n)}else"punct"===t.family?r.setAttribute("separator","true"):"open"!==t.family&&"close"!==t.family||r.setAttribute("stretchy","false");return r}});var jr={mi:"italic",mn:"normal",mtext:"normal"};Xt({type:"mathord",htmlBuilder:function(t,e){return Dt.makeOrd(t,e,"mathord")},mathmlBuilder:function(t,e){var r=new ue.MathNode("mi",[pe(t.text,t.mode,e)]),n=fe(t,e)||"italic";return n!==jr[r.type]&&r.setAttribute("mathvariant",n),r}}),Xt({type:"textord",htmlBuilder:function(t,e){return Dt.makeOrd(t,e,"textord")},mathmlBuilder:function(t,e){var r,n=pe(t.text,t.mode,e),a=fe(t,e)||"normal";return r="text"===t.mode?new ue.MathNode("mtext",[n]):/[0-9]/.test(t.text)?new ue.MathNode("mn",[n]):"\\prime"===t.text?new ue.MathNode("mo",[n]):new ue.MathNode("mi",[n]),a!==jr[r.type]&&r.setAttribute("mathvariant",a),r}});var Zr={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},Kr={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};Xt({type:"spacing",htmlBuilder:function(t,e){if(Kr.hasOwnProperty(t.text)){var r=Kr[t.text].className||"";if("text"===t.mode){var n=Dt.makeOrd(t,e,"textord");return n.classes.push(r),n}return Dt.makeSpan(["mspace",r],[Dt.mathsym(t.text,t.mode,e)],e)}if(Zr.hasOwnProperty(t.text))return Dt.makeSpan(["mspace",Zr[t.text]],[],e);throw new o('Unknown type of space "'+t.text+'"')},mathmlBuilder:function(t,e){if(!Kr.hasOwnProperty(t.text)){if(Zr.hasOwnProperty(t.text))return new ue.MathNode("mspace");throw new o('Unknown type of space "'+t.text+'"')}return new ue.MathNode("mtext",[new ue.TextNode(" ")])}});var Jr=function(){var t=new ue.MathNode("mtd",[]);return t.setAttribute("width","50%"),t};Xt({type:"tag",mathmlBuilder:function(t,e){var r=new ue.MathNode("mtable",[new ue.MathNode("mtr",[Jr(),new ue.MathNode("mtd",[xe(t.body,e)]),Jr(),new ue.MathNode("mtd",[xe(t.tag,e)])])]);return r.setAttribute("width","100%"),r}});var Qr={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},tn={"\\textbf":"textbf","\\textmd":"textmd"},en={"\\textit":"textit","\\textup":"textup"},rn=function(t,e){var r=t.font;return r?Qr[r]?e.withTextFontFamily(Qr[r]):tn[r]?e.withTextFontWeight(tn[r]):e.withTextFontShape(en[r]):e};Wt({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup"],props:{numArgs:1,argTypes:["text"],greediness:2,allowedInText:!0},handler:function(t,e){var r=t.parser,n=t.funcName,a=e[0];return{type:"text",mode:r.mode,body:jt(a),font:n}},htmlBuilder:function(t,e){var r=rn(t,e),n=ee(t.body,r,!0);return Dt.makeSpan(["mord","text"],Dt.tryCombineChars(n),r)},mathmlBuilder:function(t,e){var r=rn(t,e);return xe(t.body,r)}}),Wt({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler:function(t,e){return{type:"underline",mode:t.parser.mode,body:e[0]}},htmlBuilder:function(t,e){var r=oe(t.body,e),n=Dt.makeLineSpan("underline-line",e),a=e.fontMetrics().defaultRuleThickness,i=Dt.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:a},{type:"elem",elem:n},{type:"kern",size:3*a},{type:"elem",elem:r}]},e);return Dt.makeSpan(["mord","underline"],[i],e)},mathmlBuilder:function(t,e){var r=new ue.MathNode("mo",[new ue.TextNode("‾")]);r.setAttribute("stretchy","true");var n=new ue.MathNode("munder",[ve(t.body,e),r]);return n.setAttribute("accentunder","true"),n}}),Wt({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler:function(t,e,r){throw new o("\\verb ended by end of line instead of matching delimiter")},htmlBuilder:function(t,e){for(var r=nn(t),n=[],a=e.havingStyle(e.style.text()),i=0;i0&&(this.undefStack[this.undefStack.length-1][t]=e)}else{var a=this.undefStack[this.undefStack.length-1];a&&!a.hasOwnProperty(t)&&(a[t]=this.current[t])}this.current[t]=e},t}(),mn={},cn=mn;function un(t,e){mn[t]=e}un("\\noexpand",(function(t){var e=t.popToken();return t.isExpandable(e.text)&&(e.noexpand=!0,e.treatAsRelax=!0),{tokens:[e],numArgs:0}})),un("\\expandafter",(function(t){var e=t.popToken();return t.expandOnce(!0),{tokens:[e],numArgs:0}})),un("\\@firstoftwo",(function(t){return{tokens:t.consumeArgs(2)[0],numArgs:0}})),un("\\@secondoftwo",(function(t){return{tokens:t.consumeArgs(2)[1],numArgs:0}})),un("\\@ifnextchar",(function(t){var e=t.consumeArgs(3);t.consumeSpaces();var r=t.future();return 1===e[0].length&&e[0][0].text===r.text?{tokens:e[1],numArgs:0}:{tokens:e[2],numArgs:0}})),un("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}"),un("\\TextOrMath",(function(t){var e=t.consumeArgs(2);return"text"===t.mode?{tokens:e[0],numArgs:0}:{tokens:e[1],numArgs:0}}));var pn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};un("\\char",(function(t){var e,r=t.popToken(),n="";if("'"===r.text)e=8,r=t.popToken();else if('"'===r.text)e=16,r=t.popToken();else if("`"===r.text)if("\\"===(r=t.popToken()).text[0])n=r.text.charCodeAt(1);else{if("EOF"===r.text)throw new o("\\char` missing argument");n=r.text.charCodeAt(0)}else e=10;if(e){if(null==(n=pn[r.text])||n>=e)throw new o("Invalid base-"+e+" digit "+r.text);for(var a;null!=(a=pn[t.future().text])&&a":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};un("\\dots",(function(t){var e="\\dotso",r=t.expandAfterFuture().text;return r in fn?e=fn[r]:("\\not"===r.substr(0,4)||r in X.math&&c.contains(["bin","rel"],X.math[r].group))&&(e="\\dotsb"),e}));var gn={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};un("\\dotso",(function(t){return t.future().text in gn?"\\ldots\\,":"\\ldots"})),un("\\dotsc",(function(t){var e=t.future().text;return e in gn&&","!==e?"\\ldots\\,":"\\ldots"})),un("\\cdots",(function(t){return t.future().text in gn?"\\@cdots\\,":"\\@cdots"})),un("\\dotsb","\\cdots"),un("\\dotsm","\\cdots"),un("\\dotsi","\\!\\cdots"),un("\\dotsx","\\ldots\\,"),un("\\DOTSI","\\relax"),un("\\DOTSB","\\relax"),un("\\DOTSX","\\relax"),un("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"),un("\\,","\\tmspace+{3mu}{.1667em}"),un("\\thinspace","\\,"),un("\\>","\\mskip{4mu}"),un("\\:","\\tmspace+{4mu}{.2222em}"),un("\\medspace","\\:"),un("\\;","\\tmspace+{5mu}{.2777em}"),un("\\thickspace","\\;"),un("\\!","\\tmspace-{3mu}{.1667em}"),un("\\negthinspace","\\!"),un("\\negmedspace","\\tmspace-{4mu}{.2222em}"),un("\\negthickspace","\\tmspace-{5mu}{.277em}"),un("\\enspace","\\kern.5em "),un("\\enskip","\\hskip.5em\\relax"),un("\\quad","\\hskip1em\\relax"),un("\\qquad","\\hskip2em\\relax"),un("\\tag","\\@ifstar\\tag@literal\\tag@paren"),un("\\tag@paren","\\tag@literal{({#1})}"),un("\\tag@literal",(function(t){if(t.macros.get("\\df@tag"))throw new o("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"})),un("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"),un("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"),un("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}"),un("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"),un("\\pmb","\\html@mathml{\\@binrel{#1}{\\mathrlap{#1}\\kern0.5px#1}}{\\mathbf{#1}}"),un("\\\\","\\newline"),un("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");var xn=F["Main-Regular"]["T".charCodeAt(0)][1]-.7*F["Main-Regular"]["A".charCodeAt(0)][1]+"em";un("\\LaTeX","\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{"+xn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"),un("\\KaTeX","\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{"+xn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"),un("\\hspace","\\@ifstar\\@hspacer\\@hspace"),un("\\@hspace","\\hskip #1\\relax"),un("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax"),un("\\ordinarycolon",":"),un("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}"),un("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}'),un("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}'),un("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}'),un("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}'),un("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}'),un("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}'),un("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}'),un("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}'),un("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}'),un("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}'),un("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}'),un("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}'),un("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}'),un("∷","\\dblcolon"),un("∹","\\eqcolon"),un("≔","\\coloneqq"),un("≕","\\eqqcolon"),un("⩴","\\Coloneqq"),un("\\ratio","\\vcentcolon"),un("\\coloncolon","\\dblcolon"),un("\\colonequals","\\coloneqq"),un("\\coloncolonequals","\\Coloneqq"),un("\\equalscolon","\\eqqcolon"),un("\\equalscoloncolon","\\Eqqcolon"),un("\\colonminus","\\coloneq"),un("\\coloncolonminus","\\Coloneq"),un("\\minuscolon","\\eqcolon"),un("\\minuscoloncolon","\\Eqcolon"),un("\\coloncolonapprox","\\Colonapprox"),un("\\coloncolonsim","\\Colonsim"),un("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),un("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"),un("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),un("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"),un("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`∌}}"),un("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}"),un("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}"),un("\\gvertneqq","\\html@mathml{\\@gvertneqq}{≩}"),un("\\lvertneqq","\\html@mathml{\\@lvertneqq}{≨}"),un("\\ngeqq","\\html@mathml{\\@ngeqq}{≱}"),un("\\ngeqslant","\\html@mathml{\\@ngeqslant}{≱}"),un("\\nleqq","\\html@mathml{\\@nleqq}{≰}"),un("\\nleqslant","\\html@mathml{\\@nleqslant}{≰}"),un("\\nshortmid","\\html@mathml{\\@nshortmid}{∤}"),un("\\nshortparallel","\\html@mathml{\\@nshortparallel}{∦}"),un("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{⊈}"),un("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{⊉}"),un("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{⊊}"),un("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{⫋}"),un("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{⊋}"),un("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{⫌}"),un("\\imath","\\html@mathml{\\@imath}{ı}"),un("\\jmath","\\html@mathml{\\@jmath}{ȷ}"),un("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`⟦}}"),un("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`⟧}}"),un("⟦","\\llbracket"),un("⟧","\\rrbracket"),un("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`⦃}}"),un("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`⦄}}"),un("⦃","\\lBrace"),un("⦄","\\rBrace"),un("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`⦵}}"),un("⦵","\\minuso"),un("\\darr","\\downarrow"),un("\\dArr","\\Downarrow"),un("\\Darr","\\Downarrow"),un("\\lang","\\langle"),un("\\rang","\\rangle"),un("\\uarr","\\uparrow"),un("\\uArr","\\Uparrow"),un("\\Uarr","\\Uparrow"),un("\\N","\\mathbb{N}"),un("\\R","\\mathbb{R}"),un("\\Z","\\mathbb{Z}"),un("\\alef","\\aleph"),un("\\alefsym","\\aleph"),un("\\Alpha","\\mathrm{A}"),un("\\Beta","\\mathrm{B}"),un("\\bull","\\bullet"),un("\\Chi","\\mathrm{X}"),un("\\clubs","\\clubsuit"),un("\\cnums","\\mathbb{C}"),un("\\Complex","\\mathbb{C}"),un("\\Dagger","\\ddagger"),un("\\diamonds","\\diamondsuit"),un("\\empty","\\emptyset"),un("\\Epsilon","\\mathrm{E}"),un("\\Eta","\\mathrm{H}"),un("\\exist","\\exists"),un("\\harr","\\leftrightarrow"),un("\\hArr","\\Leftrightarrow"),un("\\Harr","\\Leftrightarrow"),un("\\hearts","\\heartsuit"),un("\\image","\\Im"),un("\\infin","\\infty"),un("\\Iota","\\mathrm{I}"),un("\\isin","\\in"),un("\\Kappa","\\mathrm{K}"),un("\\larr","\\leftarrow"),un("\\lArr","\\Leftarrow"),un("\\Larr","\\Leftarrow"),un("\\lrarr","\\leftrightarrow"),un("\\lrArr","\\Leftrightarrow"),un("\\Lrarr","\\Leftrightarrow"),un("\\Mu","\\mathrm{M}"),un("\\natnums","\\mathbb{N}"),un("\\Nu","\\mathrm{N}"),un("\\Omicron","\\mathrm{O}"),un("\\plusmn","\\pm"),un("\\rarr","\\rightarrow"),un("\\rArr","\\Rightarrow"),un("\\Rarr","\\Rightarrow"),un("\\real","\\Re"),un("\\reals","\\mathbb{R}"),un("\\Reals","\\mathbb{R}"),un("\\Rho","\\mathrm{P}"),un("\\sdot","\\cdot"),un("\\sect","\\S"),un("\\spades","\\spadesuit"),un("\\sub","\\subset"),un("\\sube","\\subseteq"),un("\\supe","\\supseteq"),un("\\Tau","\\mathrm{T}"),un("\\thetasym","\\vartheta"),un("\\weierp","\\wp"),un("\\Zeta","\\mathrm{Z}"),un("\\argmin","\\DOTSB\\operatorname*{arg\\,min}"),un("\\argmax","\\DOTSB\\operatorname*{arg\\,max}"),un("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits"),un("\\bra","\\mathinner{\\langle{#1}|}"),un("\\ket","\\mathinner{|{#1}\\rangle}"),un("\\braket","\\mathinner{\\langle{#1}\\rangle}"),un("\\Bra","\\left\\langle#1\\right|"),un("\\Ket","\\left|#1\\right\\rangle"),un("\\blue","\\textcolor{##6495ed}{#1}"),un("\\orange","\\textcolor{##ffa500}{#1}"),un("\\pink","\\textcolor{##ff00af}{#1}"),un("\\red","\\textcolor{##df0030}{#1}"),un("\\green","\\textcolor{##28ae7b}{#1}"),un("\\gray","\\textcolor{gray}{#1}"),un("\\purple","\\textcolor{##9d38bd}{#1}"),un("\\blueA","\\textcolor{##ccfaff}{#1}"),un("\\blueB","\\textcolor{##80f6ff}{#1}"),un("\\blueC","\\textcolor{##63d9ea}{#1}"),un("\\blueD","\\textcolor{##11accd}{#1}"),un("\\blueE","\\textcolor{##0c7f99}{#1}"),un("\\tealA","\\textcolor{##94fff5}{#1}"),un("\\tealB","\\textcolor{##26edd5}{#1}"),un("\\tealC","\\textcolor{##01d1c1}{#1}"),un("\\tealD","\\textcolor{##01a995}{#1}"),un("\\tealE","\\textcolor{##208170}{#1}"),un("\\greenA","\\textcolor{##b6ffb0}{#1}"),un("\\greenB","\\textcolor{##8af281}{#1}"),un("\\greenC","\\textcolor{##74cf70}{#1}"),un("\\greenD","\\textcolor{##1fab54}{#1}"),un("\\greenE","\\textcolor{##0d923f}{#1}"),un("\\goldA","\\textcolor{##ffd0a9}{#1}"),un("\\goldB","\\textcolor{##ffbb71}{#1}"),un("\\goldC","\\textcolor{##ff9c39}{#1}"),un("\\goldD","\\textcolor{##e07d10}{#1}"),un("\\goldE","\\textcolor{##a75a05}{#1}"),un("\\redA","\\textcolor{##fca9a9}{#1}"),un("\\redB","\\textcolor{##ff8482}{#1}"),un("\\redC","\\textcolor{##f9685d}{#1}"),un("\\redD","\\textcolor{##e84d39}{#1}"),un("\\redE","\\textcolor{##bc2612}{#1}"),un("\\maroonA","\\textcolor{##ffbde0}{#1}"),un("\\maroonB","\\textcolor{##ff92c6}{#1}"),un("\\maroonC","\\textcolor{##ed5fa6}{#1}"),un("\\maroonD","\\textcolor{##ca337c}{#1}"),un("\\maroonE","\\textcolor{##9e034e}{#1}"),un("\\purpleA","\\textcolor{##ddd7ff}{#1}"),un("\\purpleB","\\textcolor{##c6b9fc}{#1}"),un("\\purpleC","\\textcolor{##aa87ff}{#1}"),un("\\purpleD","\\textcolor{##7854ab}{#1}"),un("\\purpleE","\\textcolor{##543b78}{#1}"),un("\\mintA","\\textcolor{##f5f9e8}{#1}"),un("\\mintB","\\textcolor{##edf2df}{#1}"),un("\\mintC","\\textcolor{##e0e5cc}{#1}"),un("\\grayA","\\textcolor{##f6f7f7}{#1}"),un("\\grayB","\\textcolor{##f0f1f2}{#1}"),un("\\grayC","\\textcolor{##e3e5e6}{#1}"),un("\\grayD","\\textcolor{##d6d8da}{#1}"),un("\\grayE","\\textcolor{##babec2}{#1}"),un("\\grayF","\\textcolor{##888d93}{#1}"),un("\\grayG","\\textcolor{##626569}{#1}"),un("\\grayH","\\textcolor{##3b3e40}{#1}"),un("\\grayI","\\textcolor{##21242c}{#1}"),un("\\kaBlue","\\textcolor{##314453}{#1}"),un("\\kaGreen","\\textcolor{##71B307}{#1}");var vn={"\\relax":!0,"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},bn=function(){function t(t,e,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=e,this.expansionCount=0,this.feed(t),this.macros=new hn(cn,e.macros),this.mode=r,this.stack=[]}var e=t.prototype;return e.feed=function(t){this.lexer=new ln(t,this.settings)},e.switchMode=function(t){this.mode=t},e.beginGroup=function(){this.macros.beginGroup()},e.endGroup=function(){this.macros.endGroup()},e.future=function(){return 0===this.stack.length&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]},e.popToken=function(){return this.future(),this.stack.pop()},e.pushToken=function(t){this.stack.push(t)},e.pushTokens=function(t){var e;(e=this.stack).push.apply(e,t)},e.consumeSpaces=function(){for(;" "===this.future().text;)this.stack.pop()},e.consumeArgs=function(t){for(var e=[],r=0;rthis.settings.maxExpand)throw new o("Too many expansions: infinite loop or need to increase maxExpand setting");var a=n.tokens;if(n.numArgs)for(var i=this.consumeArgs(n.numArgs),s=(a=a.slice()).length-1;s>=0;--s){var l=a[s];if("#"===l.text){if(0===s)throw new o("Incomplete placeholder at end of macro body",l);if("#"===(l=a[--s]).text)a.splice(s+1,1);else{if(!/^[1-9]$/.test(l.text))throw new o("Not a valid argument number",l);var h;(h=a).splice.apply(h,[s,2].concat(i[+l.text-1]))}}}return this.pushTokens(a),a},e.expandAfterFuture=function(){return this.expandOnce(),this.future()},e.expandNextToken=function(){for(;;){var t=this.expandOnce();if(t instanceof a){if("\\relax"!==t.text&&!t.treatAsRelax)return this.stack.pop();this.stack.pop()}}throw new Error},e.expandMacro=function(t){return this.macros.has(t)?this.expandTokens([new a(t)]):void 0},e.expandTokens=function(t){var e=[],r=this.stack.length;for(this.pushTokens(t);this.stack.length>r;){var n=this.expandOnce(!0);n instanceof a&&(n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),e.push(this.stack.pop()))}return e},e.expandMacroAsText=function(t){var e=this.expandMacro(t);return e?e.map((function(t){return t.text})).join(""):e},e._getExpansion=function(t){var e=this.macros.get(t);if(null==e)return e;var r="function"==typeof e?e(this):e;if("string"==typeof r){var n=0;if(-1!==r.indexOf("#"))for(var a=r.replace(/##/g,"");-1!==a.indexOf("#"+(n+1));)++n;for(var i=new ln(r,this.settings),o=[],s=i.lex();"EOF"!==s.text;)o.push(s),s=i.lex();return o.reverse(),{tokens:o,numArgs:n}}return r},e.isDefined=function(t){return this.macros.has(t)||an.hasOwnProperty(t)||X.math.hasOwnProperty(t)||X.text.hasOwnProperty(t)||vn.hasOwnProperty(t)},e.isExpandable=function(t){var e=this.macros.get(t);return null!=e?"string"==typeof e||"function"==typeof e||!e.unexpandable:an.hasOwnProperty(t)},t}(),yn={"́":{text:"\\'",math:"\\acute"},"̀":{text:"\\`",math:"\\grave"},"̈":{text:'\\"',math:"\\ddot"},"̃":{text:"\\~",math:"\\tilde"},"̄":{text:"\\=",math:"\\bar"},"̆":{text:"\\u",math:"\\breve"},"̌":{text:"\\v",math:"\\check"},"̂":{text:"\\^",math:"\\hat"},"̇":{text:"\\.",math:"\\dot"},"̊":{text:"\\r",math:"\\mathring"},"̋":{text:"\\H"}},wn={"á":"á","à":"à","ä":"ä","ǟ":"ǟ","ã":"ã","ā":"ā","ă":"ă","ắ":"ắ","ằ":"ằ","ẵ":"ẵ","ǎ":"ǎ","â":"â","ấ":"ấ","ầ":"ầ","ẫ":"ẫ","ȧ":"ȧ","ǡ":"ǡ","å":"å","ǻ":"ǻ","ḃ":"ḃ","ć":"ć","č":"č","ĉ":"ĉ","ċ":"ċ","ď":"ď","ḋ":"ḋ","é":"é","è":"è","ë":"ë","ẽ":"ẽ","ē":"ē","ḗ":"ḗ","ḕ":"ḕ","ĕ":"ĕ","ě":"ě","ê":"ê","ế":"ế","ề":"ề","ễ":"ễ","ė":"ė","ḟ":"ḟ","ǵ":"ǵ","ḡ":"ḡ","ğ":"ğ","ǧ":"ǧ","ĝ":"ĝ","ġ":"ġ","ḧ":"ḧ","ȟ":"ȟ","ĥ":"ĥ","ḣ":"ḣ","í":"í","ì":"ì","ï":"ï","ḯ":"ḯ","ĩ":"ĩ","ī":"ī","ĭ":"ĭ","ǐ":"ǐ","î":"î","ǰ":"ǰ","ĵ":"ĵ","ḱ":"ḱ","ǩ":"ǩ","ĺ":"ĺ","ľ":"ľ","ḿ":"ḿ","ṁ":"ṁ","ń":"ń","ǹ":"ǹ","ñ":"ñ","ň":"ň","ṅ":"ṅ","ó":"ó","ò":"ò","ö":"ö","ȫ":"ȫ","õ":"õ","ṍ":"ṍ","ṏ":"ṏ","ȭ":"ȭ","ō":"ō","ṓ":"ṓ","ṑ":"ṑ","ŏ":"ŏ","ǒ":"ǒ","ô":"ô","ố":"ố","ồ":"ồ","ỗ":"ỗ","ȯ":"ȯ","ȱ":"ȱ","ő":"ő","ṕ":"ṕ","ṗ":"ṗ","ŕ":"ŕ","ř":"ř","ṙ":"ṙ","ś":"ś","ṥ":"ṥ","š":"š","ṧ":"ṧ","ŝ":"ŝ","ṡ":"ṡ","ẗ":"ẗ","ť":"ť","ṫ":"ṫ","ú":"ú","ù":"ù","ü":"ü","ǘ":"ǘ","ǜ":"ǜ","ǖ":"ǖ","ǚ":"ǚ","ũ":"ũ","ṹ":"ṹ","ū":"ū","ṻ":"ṻ","ŭ":"ŭ","ǔ":"ǔ","û":"û","ů":"ů","ű":"ű","ṽ":"ṽ","ẃ":"ẃ","ẁ":"ẁ","ẅ":"ẅ","ŵ":"ŵ","ẇ":"ẇ","ẘ":"ẘ","ẍ":"ẍ","ẋ":"ẋ","ý":"ý","ỳ":"ỳ","ÿ":"ÿ","ỹ":"ỹ","ȳ":"ȳ","ŷ":"ŷ","ẏ":"ẏ","ẙ":"ẙ","ź":"ź","ž":"ž","ẑ":"ẑ","ż":"ż","Á":"Á","À":"À","Ä":"Ä","Ǟ":"Ǟ","Ã":"Ã","Ā":"Ā","Ă":"Ă","Ắ":"Ắ","Ằ":"Ằ","Ẵ":"Ẵ","Ǎ":"Ǎ","Â":"Â","Ấ":"Ấ","Ầ":"Ầ","Ẫ":"Ẫ","Ȧ":"Ȧ","Ǡ":"Ǡ","Å":"Å","Ǻ":"Ǻ","Ḃ":"Ḃ","Ć":"Ć","Č":"Č","Ĉ":"Ĉ","Ċ":"Ċ","Ď":"Ď","Ḋ":"Ḋ","É":"É","È":"È","Ë":"Ë","Ẽ":"Ẽ","Ē":"Ē","Ḗ":"Ḗ","Ḕ":"Ḕ","Ĕ":"Ĕ","Ě":"Ě","Ê":"Ê","Ế":"Ế","Ề":"Ề","Ễ":"Ễ","Ė":"Ė","Ḟ":"Ḟ","Ǵ":"Ǵ","Ḡ":"Ḡ","Ğ":"Ğ","Ǧ":"Ǧ","Ĝ":"Ĝ","Ġ":"Ġ","Ḧ":"Ḧ","Ȟ":"Ȟ","Ĥ":"Ĥ","Ḣ":"Ḣ","Í":"Í","Ì":"Ì","Ï":"Ï","Ḯ":"Ḯ","Ĩ":"Ĩ","Ī":"Ī","Ĭ":"Ĭ","Ǐ":"Ǐ","Î":"Î","İ":"İ","Ĵ":"Ĵ","Ḱ":"Ḱ","Ǩ":"Ǩ","Ĺ":"Ĺ","Ľ":"Ľ","Ḿ":"Ḿ","Ṁ":"Ṁ","Ń":"Ń","Ǹ":"Ǹ","Ñ":"Ñ","Ň":"Ň","Ṅ":"Ṅ","Ó":"Ó","Ò":"Ò","Ö":"Ö","Ȫ":"Ȫ","Õ":"Õ","Ṍ":"Ṍ","Ṏ":"Ṏ","Ȭ":"Ȭ","Ō":"Ō","Ṓ":"Ṓ","Ṑ":"Ṑ","Ŏ":"Ŏ","Ǒ":"Ǒ","Ô":"Ô","Ố":"Ố","Ồ":"Ồ","Ỗ":"Ỗ","Ȯ":"Ȯ","Ȱ":"Ȱ","Ő":"Ő","Ṕ":"Ṕ","Ṗ":"Ṗ","Ŕ":"Ŕ","Ř":"Ř","Ṙ":"Ṙ","Ś":"Ś","Ṥ":"Ṥ","Š":"Š","Ṧ":"Ṧ","Ŝ":"Ŝ","Ṡ":"Ṡ","Ť":"Ť","Ṫ":"Ṫ","Ú":"Ú","Ù":"Ù","Ü":"Ü","Ǘ":"Ǘ","Ǜ":"Ǜ","Ǖ":"Ǖ","Ǚ":"Ǚ","Ũ":"Ũ","Ṹ":"Ṹ","Ū":"Ū","Ṻ":"Ṻ","Ŭ":"Ŭ","Ǔ":"Ǔ","Û":"Û","Ů":"Ů","Ű":"Ű","Ṽ":"Ṽ","Ẃ":"Ẃ","Ẁ":"Ẁ","Ẅ":"Ẅ","Ŵ":"Ŵ","Ẇ":"Ẇ","Ẍ":"Ẍ","Ẋ":"Ẋ","Ý":"Ý","Ỳ":"Ỳ","Ÿ":"Ÿ","Ỹ":"Ỹ","Ȳ":"Ȳ","Ŷ":"Ŷ","Ẏ":"Ẏ","Ź":"Ź","Ž":"Ž","Ẑ":"Ẑ","Ż":"Ż","ά":"ά","ὰ":"ὰ","ᾱ":"ᾱ","ᾰ":"ᾰ","έ":"έ","ὲ":"ὲ","ή":"ή","ὴ":"ὴ","ί":"ί","ὶ":"ὶ","ϊ":"ϊ","ΐ":"ΐ","ῒ":"ῒ","ῑ":"ῑ","ῐ":"ῐ","ό":"ό","ὸ":"ὸ","ύ":"ύ","ὺ":"ὺ","ϋ":"ϋ","ΰ":"ΰ","ῢ":"ῢ","ῡ":"ῡ","ῠ":"ῠ","ώ":"ώ","ὼ":"ὼ","Ύ":"Ύ","Ὺ":"Ὺ","Ϋ":"Ϋ","Ῡ":"Ῡ","Ῠ":"Ῠ","Ώ":"Ώ","Ὼ":"Ὼ"},kn=function(){function t(t,e){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new bn(t,e,this.mode),this.settings=e,this.leftrightDepth=0}var e=t.prototype;return e.expect=function(t,e){if(void 0===e&&(e=!0),this.fetch().text!==t)throw new o("Expected '"+t+"', got '"+this.fetch().text+"'",this.fetch());e&&this.consume()},e.consume=function(){this.nextToken=null},e.fetch=function(){return null==this.nextToken&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken},e.switchMode=function(t){this.mode=t,this.gullet.switchMode(t)},e.parse=function(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");var t=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),t},e.parseExpression=function(e,r){for(var n=[];;){"math"===this.mode&&this.consumeSpaces();var a=this.fetch();if(-1!==t.endOfExpression.indexOf(a.text))break;if(r&&a.text===r)break;if(e&&an[a.text]&&an[a.text].infix)break;var i=this.parseAtom(r);if(!i)break;"internal"!==i.type&&n.push(i)}return"text"===this.mode&&this.formLigatures(n),this.handleInfixNodes(n)},e.handleInfixNodes=function(t){for(var e,r=-1,n=0;n0&&!h||0===s&&!h&&"math"===this.mode,c=this.parseGroupOfType("argument to '"+t+"'",l,h,n,m);if(!c){if(h){i.push(null);continue}throw new o("Expected group after '"+t+"'",this.fetch())}(h?i:a).push(c)}return{args:a,optArgs:i}},e.parseGroupOfType=function(t,e,r,n,a){switch(e){case"color":return a&&this.consumeSpaces(),this.parseColorGroup(r);case"size":return a&&this.consumeSpaces(),this.parseSizeGroup(r);case"url":return this.parseUrlGroup(r,a);case"math":case"text":return this.parseGroup(t,r,n,void 0,e,a);case"hbox":var i=this.parseGroup(t,r,n,void 0,"text",a);return i?{type:"styling",mode:i.mode,body:[i],style:"text"}:i;case"raw":if(a&&this.consumeSpaces(),r&&"{"===this.fetch().text)return null;var s=this.parseStringGroup("raw",r,!0);if(s)return{type:"raw",mode:"text",string:s.text};throw new o("Expected raw group",this.fetch());case"original":case null:case void 0:return this.parseGroup(t,r,n,void 0,void 0,a);default:throw new o("Unknown group type as "+t,this.fetch())}},e.consumeSpaces=function(){for(;" "===this.fetch().text;)this.consume()},e.parseStringGroup=function(t,e,r){var n=e?"[":"{",a=e?"]":"}",i=this.fetch();if(i.text!==n){if(e)return null;if(r&&"EOF"!==i.text&&/[^{}[\]]/.test(i.text))return this.consume(),i}var s=this.mode;this.mode="text",this.expect(n);for(var l,h="",m=this.fetch(),c=0,u=m;(l=this.fetch()).text!==a||r&&c>0;){switch(l.text){case"EOF":throw new o("Unexpected end of input in "+t,m.range(u,h));case n:c++;break;case a:c--}h+=(u=l).text,this.consume()}return this.expect(a),this.mode=s,m.range(u,h)},e.parseRegexGroup=function(t,e){var r=this.mode;this.mode="text";for(var n,a=this.fetch(),i=a,s="";"EOF"!==(n=this.fetch()).text&&t.test(s+n.text);)s+=(i=n).text,this.consume();if(""===s)throw new o("Invalid "+e+": '"+a.text+"'",a);return this.mode=r,a.range(i,s)},e.parseColorGroup=function(t){var e=this.parseStringGroup("color",t);if(!e)return null;var r=/^(#[a-f0-9]{3}|#?[a-f0-9]{6}|[a-z]+)$/i.exec(e.text);if(!r)throw new o("Invalid color: '"+e.text+"'",e);var n=r[0];return/^[0-9a-f]{6}$/i.test(n)&&(n="#"+n),{type:"color-token",mode:this.mode,color:n}},e.parseSizeGroup=function(t){var e,r=!1;if(!(e=t||"{"===this.fetch().text?this.parseStringGroup("size",t):this.parseRegexGroup(/^[-+]? *(?:$|\d+|\d+\.\d*|\.\d*) *[a-z]{0,2} *$/,"size")))return null;t||0!==e.text.length||(e.text="0pt",r=!0);var n=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e.text);if(!n)throw new o("Invalid size: '"+e.text+"'",e);var a={number:+(n[1]+n[2]),unit:n[3]};if(!At(a))throw new o("Invalid unit: '"+a.unit+"'",e);return{type:"size",mode:this.mode,value:a,isBlank:r}},e.parseUrlGroup=function(t,e){this.gullet.lexer.setCatcode("%",13);var r=this.parseStringGroup("url",t,!0);if(this.gullet.lexer.setCatcode("%",14),!r)return null;var n=r.text.replace(/\\([#$%&~_^{}])/g,"$1");return{type:"url",mode:this.mode,url:n}},e.parseGroup=function(e,r,a,i,s,l){var h=this.mode;s&&this.switchMode(s),l&&this.consumeSpaces();var m,c=this.fetch(),u=c.text;if(r?"["===u:"{"===u||"\\begingroup"===u){this.consume();var p=t.endOfGroup[u];this.gullet.beginGroup();var d=this.parseExpression(!1,p),f=this.fetch();this.expect(p),this.gullet.endGroup(),m={type:"ordgroup",mode:this.mode,loc:n.range(c,f),body:d,semisimple:"\\begingroup"===u||void 0}}else if(r)m=null;else if(null==(m=this.parseFunction(i,e,a)||this.parseSymbol())&&"\\"===u[0]&&!vn.hasOwnProperty(u)){if(this.settings.throwOnError)throw new o("Undefined control sequence: "+u,c);m=this.formatUnsupportedCmd(u),this.consume()}return s&&this.switchMode(h),m},e.formLigatures=function(t){for(var e=t.length-1,r=0;r=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+e[0]+'" used in math mode',t);var l,h=X[this.mode][e].group,m=n.range(t);if(Y.hasOwnProperty(h)){var c=h;l={type:"atom",mode:this.mode,family:c,loc:m,text:e}}else l={type:h,mode:this.mode,loc:m,text:e};i=l}else{if(!(e.charCodeAt(0)>=128))return null;this.settings.strict&&(M(e.charCodeAt(0))?"math"===this.mode&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+e[0]+'" used in math mode',t):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+e[0]+'" ('+e.charCodeAt(0)+")",t)),i={type:"textord",mode:"text",loc:n.range(t),text:e}}if(this.consume(),s)for(var u=0;u void) => void ); -const styleHref = (document.currentScript as any).src.replace('notebook-out/katex.js', '') + 'node_modules/katex/dist/katex.min.css'; +const styleHref = (document.currentScript as any).src.replace(/katex.js$/, 'katex.min.css'); const link = document.createElement('link'); link.rel = 'stylesheet'; diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/notebook-markdown-extensions/package.json index 74f29cfc77d..d5cd3bb4c0a 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/notebook-markdown-extensions/package.json @@ -31,15 +31,14 @@ "scripts": { "compile": "npm run build-notebook", "watch": "npm run build-notebook", - "build-notebook": "npx webpack-cli --config webpack.notebook.js --mode production" - }, - "dependencies": { - "@iktakahiro/markdown-it-katex": "^4.0.1", - "markdown-it-emoji": "^2.0.0" + "build-notebook": "npx webpack-cli --config webpack.notebook.js --mode production", + "postinstall": "npm run build-notebook" }, "devDependencies": { + "@iktakahiro/markdown-it-katex": "^4.0.1", "@types/markdown-it": "^0.0.0", - "markdown-it": "^12.0.4" + "markdown-it": "^12.0.4", + "markdown-it-emoji": "^2.0.0" }, "repository": { "type": "git", diff --git a/extensions/notebook-markdown-extensions/webpack.notebook.js b/extensions/notebook-markdown-extensions/webpack.notebook.js index dc60df5bc11..18044ea235a 100644 --- a/extensions/notebook-markdown-extensions/webpack.notebook.js +++ b/extensions/notebook-markdown-extensions/webpack.notebook.js @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ const path = require('path'); +const CopyPlugin = require('copy-webpack-plugin'); module.exports = { entry: { @@ -23,5 +24,20 @@ module.exports = { output: { filename: '[name].js', path: path.resolve(__dirname, 'notebook-out') - } + }, + plugins: [ + // @ts-ignore + new CopyPlugin({ + patterns: [ + { + from: './node_modules/katex/dist/katex.min.css', + to: 'katex.min.css' + }, + { + from: './node_modules/katex/dist/fonts', + to: 'fonts/' + }, + ], + }), + ] }; diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index dee763ae1b7..5a3d1a53f24 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -6,7 +6,7 @@ import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window, Position, ExtensionContext, env, - ShellQuotedString, ShellQuoting, commands, Location + ShellQuotedString, ShellQuoting, commands, Location, CancellationTokenSource } from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; @@ -365,7 +365,11 @@ export function getPackageJsonUriFromTask(task: Task): Uri | null { } export async function hasPackageJson(): Promise { - const files = await workspace.findFiles('**/package.json', undefined, 1); + const token = new CancellationTokenSource(); + // Search for files for max 1 second. + const timeout = setTimeout(() => token.cancel(), 1000); + const files = await workspace.findFiles('**/package.json', undefined, 1, token.token); + clearTimeout(timeout); return files.length > 0; } diff --git a/extensions/postinstall.js b/extensions/postinstall.js index da4fa3e9d04..d509a0b327c 100644 --- a/extensions/postinstall.js +++ b/extensions/postinstall.js @@ -8,7 +8,6 @@ const fs = require('fs'); const path = require('path'); -const rimraf = require('rimraf'); const root = path.join(__dirname, 'node_modules', 'typescript'); @@ -21,7 +20,7 @@ function processRoot() { if (!toKeep.has(name)) { const filePath = path.join(root, name); console.log(`Removed ${filePath}`); - rimraf.sync(filePath); + fs.rmdirSync(filePath, { recursive: true }); } } } diff --git a/extensions/simple-browser/media/index.js b/extensions/simple-browser/media/index.js index d74299e1e71..c9a5b89b490 100644 --- a/extensions/simple-browser/media/index.js +++ b/extensions/simple-browser/media/index.js @@ -1,2 +1,2 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1),r=acquireVsCodeApi();const c=function(){const e=document.getElementById("simple-browser-settings");if(e){const t=e.getAttribute("data-settings");if(t)return JSON.parse(t)}throw new Error("Could not load settings")}(),a=document.querySelector("iframe"),u=document.querySelector(".header"),d=u.querySelector(".url-input"),i=u.querySelector(".forward-button"),s=u.querySelector(".back-button"),l=u.querySelector(".reload-button"),f=u.querySelector(".open-external-button");function y(e){document.body.classList.toggle("enable-focus-lock-indicator",e)}window.addEventListener("message",e=>{switch(e.data.type){case"focus":a.focus();break;case"didChangeFocusLockIndicatorEnabled":y(e.data.enabled)}}),o.onceDocumentLoaded(()=>{function e(e){try{const t=new URL(e);t.searchParams.append("vscodeBrowserReqId",Date.now().toString()),a.src=t.toString()}catch(t){a.src=e}}setInterval(()=>{var e;const t="IFRAME"===(null===(e=document.activeElement)||void 0===e?void 0:e.tagName);document.body.classList.toggle("iframe-focused",t)},50),a.addEventListener("load",()=>{}),d.addEventListener("change",t=>{e(t.target.value)}),i.addEventListener("click",()=>{history.forward()}),s.addEventListener("click",()=>{history.back()}),f.addEventListener("click",()=>{r.postMessage({type:"openExternal",url:d.value})}),l.addEventListener("click",()=>{a.src=d.value}),e(c.url),d.value=c.url,y(c.focusLockIndicatorEnabled)})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=void 0,t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}}]); +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});const o=n(1),r=acquireVsCodeApi();const c=function(){const e=document.getElementById("simple-browser-settings");if(e){const t=e.getAttribute("data-settings");if(t)return JSON.parse(t)}throw new Error("Could not load settings")}(),a=document.querySelector("iframe"),u=document.querySelector(".header"),d=u.querySelector(".url-input"),i=u.querySelector(".forward-button"),l=u.querySelector(".back-button"),s=u.querySelector(".reload-button"),f=u.querySelector(".open-external-button");function y(e){document.body.classList.toggle("enable-focus-lock-indicator",e)}window.addEventListener("message",e=>{switch(e.data.type){case"focus":a.focus();break;case"didChangeFocusLockIndicatorEnabled":y(e.data.enabled)}}),(0,o.onceDocumentLoaded)(()=>{function e(e){try{const t=new URL(e);t.searchParams.append("vscodeBrowserReqId",Date.now().toString()),a.src=t.toString()}catch(t){a.src=e}}setInterval(()=>{var e;const t="IFRAME"===(null===(e=document.activeElement)||void 0===e?void 0:e.tagName);document.body.classList.toggle("iframe-focused",t)},50),a.addEventListener("load",()=>{}),d.addEventListener("change",t=>{e(t.target.value)}),i.addEventListener("click",()=>{history.forward()}),l.addEventListener("click",()=>{history.back()}),f.addEventListener("click",()=>{r.postMessage({type:"openExternal",url:d.value})}),s.addEventListener("click",()=>{e(d.value)}),e(c.url),d.value=c.url,y(c.focusLockIndicatorEnabled)})},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.onceDocumentLoaded=void 0,t.onceDocumentLoaded=function(e){"loading"===document.readyState||"uninitialized"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}}]); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/extensions/simple-browser/media/index.js.map b/extensions/simple-browser/media/index.js.map index df62d30257b..614e719426b 100644 --- a/extensions/simple-browser/media/index.js.map +++ b/extensions/simple-browser/media/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./preview-src/index.ts","webpack:///./preview-src/events.ts"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","events_1","vscode","acquireVsCodeApi","settings","element","document","getElementById","data","getAttribute","JSON","parse","Error","getSettings","iframe","querySelector","header","input","forwardButton","backButton","reloadButton","openExternalButton","toggleFocusLockIndicatorEnabled","enabled","body","classList","toggle","window","addEventListener","e","type","focus","onceDocumentLoaded","navigateTo","rawUrl","url","URL","searchParams","append","Date","now","toString","src","_a","setInterval","iframeFocused","activeElement","tagName","target","history","forward","back","postMessage","focusLockIndicatorEnabled","f","readyState"],"mappings":"aACE,IAAIA,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUC,QAGnC,IAAIC,EAASJ,EAAiBE,GAAY,CACzCG,EAAGH,EACHI,GAAG,EACHH,QAAS,IAUV,OANAI,EAAQL,GAAUM,KAAKJ,EAAOD,QAASC,EAAQA,EAAOD,QAASF,GAG/DG,EAAOE,GAAI,EAGJF,EAAOD,QAKfF,EAAoBQ,EAAIF,EAGxBN,EAAoBS,EAAIV,EAGxBC,EAAoBU,EAAI,SAASR,EAASS,EAAMC,GAC3CZ,EAAoBa,EAAEX,EAASS,IAClCG,OAAOC,eAAeb,EAASS,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEZ,EAAoBkB,EAAI,SAAShB,GACX,oBAAXiB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAeb,EAASiB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,KAQvDrB,EAAoBsB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQrB,EAAoBqB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFA1B,EAAoBkB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOrB,EAAoBU,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRzB,EAAoB6B,EAAI,SAAS1B,GAChC,IAAIS,EAAST,GAAUA,EAAOqB,WAC7B,WAAwB,OAAOrB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAH,EAAoBU,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRZ,EAAoBa,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG/B,EAAoBkC,EAAI,GAIjBlC,EAAoBA,EAAoBmC,EAAI,G,+BC7ErDrB,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,IACtD,MAAMe,EAAW,EAAQ,GACnBC,EAASC,mBAWf,MAAMC,EAVN,WACI,MAAMC,EAAUC,SAASC,eAAe,2BACxC,GAAIF,EAAS,CACT,MAAMG,EAAOH,EAAQI,aAAa,iBAClC,GAAID,EACA,OAAOE,KAAKC,MAAMH,GAG1B,MAAM,IAAII,MAAM,2BAEHC,GACXC,EAASR,SAASS,cAAc,UAChCC,EAASV,SAASS,cAAc,WAChCE,EAAQD,EAAOD,cAAc,cAC7BG,EAAgBF,EAAOD,cAAc,mBACrCI,EAAaH,EAAOD,cAAc,gBAClCK,EAAeJ,EAAOD,cAAc,kBACpCM,EAAqBL,EAAOD,cAAc,yBA8DhD,SAASO,EAAgCC,GACrCjB,SAASkB,KAAKC,UAAUC,OAAO,8BAA+BH,GA9DlEI,OAAOC,iBAAiB,UAAWC,IAC/B,OAAQA,EAAErB,KAAKsB,MACX,IAAK,QAEGhB,EAAOiB,QACP,MAER,IAAK,qCAEGT,EAAgCO,EAAErB,KAAKe,YAKvDtB,EAAS+B,mBAAmB,KAkCxB,SAASC,EAAWC,GAChB,IACI,MAAMC,EAAM,IAAIC,IAAIF,GAGpBC,EAAIE,aAAaC,OAAO,qBAAsBC,KAAKC,MAAMC,YACzD3B,EAAO4B,IAAMP,EAAIM,WAErB,MAAOE,GACH7B,EAAO4B,IAAMR,GA1CrBU,YAAY,KACR,IAAID,EACJ,MAAME,EAAoG,YAAjD,QAAjCF,EAAKrC,SAASwC,qBAAkC,IAAPH,OAAgB,EAASA,EAAGI,SAC7FzC,SAASkB,KAAKC,UAAUC,OAAO,iBAAkBmB,IAClD,IACH/B,EAAOc,iBAAiB,OAAQ,QAGhCX,EAAMW,iBAAiB,SAAUC,IAE7BI,EADYJ,EAAEmB,OAAO9D,SAGzBgC,EAAcU,iBAAiB,QAAS,KACpCqB,QAAQC,YAEZ/B,EAAWS,iBAAiB,QAAS,KACjCqB,QAAQE,SAEZ9B,EAAmBO,iBAAiB,QAAS,KACzC1B,EAAOkD,YAAY,CACftB,KAAM,eACNK,IAAKlB,EAAM/B,UAGnBkC,EAAaQ,iBAAiB,QAAS,KAInCd,EAAO4B,IAAMzB,EAAM/B,QAEvB+C,EAAW7B,EAAS+B,KACpBlB,EAAM/B,MAAQkB,EAAS+B,IACvBb,EAAgClB,EAASiD,8B,6BCpE7C1E,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,IACtDnB,EAAQiE,wBAAqB,EAS7BjE,EAAQiE,mBARR,SAA4BsB,GACI,YAAxBhD,SAASiD,YAAoD,kBAAxBjD,SAASiD,WAC9CjD,SAASsB,iBAAiB,mBAAoB0B,GAG9CA","file":"index.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n","\"use strict\";\n/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst events_1 = require(\"./events\");\nconst vscode = acquireVsCodeApi();\nfunction getSettings() {\n const element = document.getElementById('simple-browser-settings');\n if (element) {\n const data = element.getAttribute('data-settings');\n if (data) {\n return JSON.parse(data);\n }\n }\n throw new Error(`Could not load settings`);\n}\nconst settings = getSettings();\nconst iframe = document.querySelector('iframe');\nconst header = document.querySelector('.header');\nconst input = header.querySelector('.url-input');\nconst forwardButton = header.querySelector('.forward-button');\nconst backButton = header.querySelector('.back-button');\nconst reloadButton = header.querySelector('.reload-button');\nconst openExternalButton = header.querySelector('.open-external-button');\nwindow.addEventListener('message', e => {\n switch (e.data.type) {\n case 'focus':\n {\n iframe.focus();\n break;\n }\n case 'didChangeFocusLockIndicatorEnabled':\n {\n toggleFocusLockIndicatorEnabled(e.data.enabled);\n break;\n }\n }\n});\nevents_1.onceDocumentLoaded(() => {\n setInterval(() => {\n var _a;\n const iframeFocused = ((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.tagName) === 'IFRAME';\n document.body.classList.toggle('iframe-focused', iframeFocused);\n }, 50);\n iframe.addEventListener('load', () => {\n // Noop\n });\n input.addEventListener('change', e => {\n const url = e.target.value;\n navigateTo(url);\n });\n forwardButton.addEventListener('click', () => {\n history.forward();\n });\n backButton.addEventListener('click', () => {\n history.back();\n });\n openExternalButton.addEventListener('click', () => {\n vscode.postMessage({\n type: 'openExternal',\n url: input.value\n });\n });\n reloadButton.addEventListener('click', () => {\n // This does not seem to trigger what we want\n // history.go(0);\n // This incorrectly adds entries to the history but does reload\n iframe.src = input.value;\n });\n navigateTo(settings.url);\n input.value = settings.url;\n toggleFocusLockIndicatorEnabled(settings.focusLockIndicatorEnabled);\n function navigateTo(rawUrl) {\n try {\n const url = new URL(rawUrl);\n // Try to bust the cache for the iframe\n // There does not appear to be any way to reliably do this except modifying the url\n url.searchParams.append('vscodeBrowserReqId', Date.now().toString());\n iframe.src = url.toString();\n }\n catch (_a) {\n iframe.src = rawUrl;\n }\n }\n});\nfunction toggleFocusLockIndicatorEnabled(enabled) {\n document.body.classList.toggle('enable-focus-lock-indicator', enabled);\n}\n","\"use strict\";\n/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.onceDocumentLoaded = void 0;\nfunction onceDocumentLoaded(f) {\n if (document.readyState === 'loading' || document.readyState === 'uninitialized') {\n document.addEventListener('DOMContentLoaded', f);\n }\n else {\n f();\n }\n}\nexports.onceDocumentLoaded = onceDocumentLoaded;\n"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///./preview-src/index.ts","webpack:///./preview-src/events.ts"],"names":["installedModules","__webpack_require__","moduleId","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","events_1","vscode","acquireVsCodeApi","settings","element","document","getElementById","data","getAttribute","JSON","parse","Error","getSettings","iframe","querySelector","header","input","forwardButton","backButton","reloadButton","openExternalButton","toggleFocusLockIndicatorEnabled","enabled","body","classList","toggle","window","addEventListener","e","type","focus","onceDocumentLoaded","navigateTo","rawUrl","url","URL","searchParams","append","Date","now","toString","src","_a","setInterval","iframeFocused","activeElement","tagName","target","history","forward","back","postMessage","focusLockIndicatorEnabled","f","readyState"],"mappings":"aACE,IAAIA,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUC,QAGnC,IAAIC,EAASJ,EAAiBE,GAAY,CACzCG,EAAGH,EACHI,GAAG,EACHH,QAAS,IAUV,OANAI,EAAQL,GAAUM,KAAKJ,EAAOD,QAASC,EAAQA,EAAOD,QAASF,GAG/DG,EAAOE,GAAI,EAGJF,EAAOD,QAKfF,EAAoBQ,EAAIF,EAGxBN,EAAoBS,EAAIV,EAGxBC,EAAoBU,EAAI,SAASR,EAASS,EAAMC,GAC3CZ,EAAoBa,EAAEX,EAASS,IAClCG,OAAOC,eAAeb,EAASS,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEZ,EAAoBkB,EAAI,SAAShB,GACX,oBAAXiB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAeb,EAASiB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,KAQvDrB,EAAoBsB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQrB,EAAoBqB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFA1B,EAAoBkB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOrB,EAAoBU,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRzB,EAAoB6B,EAAI,SAAS1B,GAChC,IAAIS,EAAST,GAAUA,EAAOqB,WAC7B,WAAwB,OAAOrB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAH,EAAoBU,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRZ,EAAoBa,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG/B,EAAoBkC,EAAI,GAIjBlC,EAAoBA,EAAoBmC,EAAI,G,+BC7ErDrB,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,IACtD,MAAMe,EAAW,EAAQ,GACnBC,EAASC,mBAWf,MAAMC,EAVN,WACI,MAAMC,EAAUC,SAASC,eAAe,2BACxC,GAAIF,EAAS,CACT,MAAMG,EAAOH,EAAQI,aAAa,iBAClC,GAAID,EACA,OAAOE,KAAKC,MAAMH,GAG1B,MAAM,IAAII,MAAM,2BAEHC,GACXC,EAASR,SAASS,cAAc,UAChCC,EAASV,SAASS,cAAc,WAChCE,EAAQD,EAAOD,cAAc,cAC7BG,EAAgBF,EAAOD,cAAc,mBACrCI,EAAaH,EAAOD,cAAc,gBAClCK,EAAeJ,EAAOD,cAAc,kBACpCM,EAAqBL,EAAOD,cAAc,yBAgEhD,SAASO,EAAgCC,GACrCjB,SAASkB,KAAKC,UAAUC,OAAO,8BAA+BH,GAhElEI,OAAOC,iBAAiB,UAAWC,IAC/B,OAAQA,EAAErB,KAAKsB,MACX,IAAK,QAEGhB,EAAOiB,QACP,MAER,IAAK,qCAEGT,EAAgCO,EAAErB,KAAKe,aAKvD,EAAItB,EAAS+B,oBAAoB,KAoC7B,SAASC,EAAWC,GAChB,IACI,MAAMC,EAAM,IAAIC,IAAIF,GAGpBC,EAAIE,aAAaC,OAAO,qBAAsBC,KAAKC,MAAMC,YACzD3B,EAAO4B,IAAMP,EAAIM,WAErB,MAAOE,GACH7B,EAAO4B,IAAMR,GA5CrBU,YAAY,KACR,IAAID,EACJ,MAAME,EAAoG,YAAjD,QAAjCF,EAAKrC,SAASwC,qBAAkC,IAAPH,OAAgB,EAASA,EAAGI,SAC7FzC,SAASkB,KAAKC,UAAUC,OAAO,iBAAkBmB,IAClD,IACH/B,EAAOc,iBAAiB,OAAQ,QAGhCX,EAAMW,iBAAiB,SAAUC,IAE7BI,EADYJ,EAAEmB,OAAO9D,SAGzBgC,EAAcU,iBAAiB,QAAS,KACpCqB,QAAQC,YAEZ/B,EAAWS,iBAAiB,QAAS,KACjCqB,QAAQE,SAEZ9B,EAAmBO,iBAAiB,QAAS,KACzC1B,EAAOkD,YAAY,CACftB,KAAM,eACNK,IAAKlB,EAAM/B,UAGnBkC,EAAaQ,iBAAiB,QAAS,KAMnCK,EAAWhB,EAAM/B,SAErB+C,EAAW7B,EAAS+B,KACpBlB,EAAM/B,MAAQkB,EAAS+B,IACvBb,EAAgClB,EAASiD,8B,6BCtE7C1E,OAAOC,eAAeb,EAAS,aAAc,CAAEmB,OAAO,IACtDnB,EAAQiE,wBAAqB,EAS7BjE,EAAQiE,mBARR,SAA4BsB,GACI,YAAxBhD,SAASiD,YAAoD,kBAAxBjD,SAASiD,WAC9CjD,SAASsB,iBAAiB,mBAAoB0B,GAG9CA","file":"index.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n","\"use strict\";\n/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nObject.defineProperty(exports, \"__esModule\", { value: true });\nconst events_1 = require(\"./events\");\nconst vscode = acquireVsCodeApi();\nfunction getSettings() {\n const element = document.getElementById('simple-browser-settings');\n if (element) {\n const data = element.getAttribute('data-settings');\n if (data) {\n return JSON.parse(data);\n }\n }\n throw new Error(`Could not load settings`);\n}\nconst settings = getSettings();\nconst iframe = document.querySelector('iframe');\nconst header = document.querySelector('.header');\nconst input = header.querySelector('.url-input');\nconst forwardButton = header.querySelector('.forward-button');\nconst backButton = header.querySelector('.back-button');\nconst reloadButton = header.querySelector('.reload-button');\nconst openExternalButton = header.querySelector('.open-external-button');\nwindow.addEventListener('message', e => {\n switch (e.data.type) {\n case 'focus':\n {\n iframe.focus();\n break;\n }\n case 'didChangeFocusLockIndicatorEnabled':\n {\n toggleFocusLockIndicatorEnabled(e.data.enabled);\n break;\n }\n }\n});\n(0, events_1.onceDocumentLoaded)(() => {\n setInterval(() => {\n var _a;\n const iframeFocused = ((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.tagName) === 'IFRAME';\n document.body.classList.toggle('iframe-focused', iframeFocused);\n }, 50);\n iframe.addEventListener('load', () => {\n // Noop\n });\n input.addEventListener('change', e => {\n const url = e.target.value;\n navigateTo(url);\n });\n forwardButton.addEventListener('click', () => {\n history.forward();\n });\n backButton.addEventListener('click', () => {\n history.back();\n });\n openExternalButton.addEventListener('click', () => {\n vscode.postMessage({\n type: 'openExternal',\n url: input.value\n });\n });\n reloadButton.addEventListener('click', () => {\n // This does not seem to trigger what we want\n // history.go(0);\n // This incorrectly adds entries to the history but does reload\n // It also always incorrectly always loads the value in the input bar,\n // which may not match the current page if the user has navigated\n navigateTo(input.value);\n });\n navigateTo(settings.url);\n input.value = settings.url;\n toggleFocusLockIndicatorEnabled(settings.focusLockIndicatorEnabled);\n function navigateTo(rawUrl) {\n try {\n const url = new URL(rawUrl);\n // Try to bust the cache for the iframe\n // There does not appear to be any way to reliably do this except modifying the url\n url.searchParams.append('vscodeBrowserReqId', Date.now().toString());\n iframe.src = url.toString();\n }\n catch (_a) {\n iframe.src = rawUrl;\n }\n }\n});\nfunction toggleFocusLockIndicatorEnabled(enabled) {\n document.body.classList.toggle('enable-focus-lock-indicator', enabled);\n}\n","\"use strict\";\n/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.onceDocumentLoaded = void 0;\nfunction onceDocumentLoaded(f) {\n if (document.readyState === 'loading' || document.readyState === 'uninitialized') {\n document.addEventListener('DOMContentLoaded', f);\n }\n else {\n f();\n }\n}\nexports.onceDocumentLoaded = onceDocumentLoaded;\n"],"sourceRoot":""} \ No newline at end of file diff --git a/extensions/simple-browser/preview-src/index.ts b/extensions/simple-browser/preview-src/index.ts index 83cb3dedb16..36fe4eb8e15 100644 --- a/extensions/simple-browser/preview-src/index.ts +++ b/extensions/simple-browser/preview-src/index.ts @@ -80,7 +80,9 @@ onceDocumentLoaded(() => { // history.go(0); // This incorrectly adds entries to the history but does reload - iframe.src = input.value; + // It also always incorrectly always loads the value in the input bar, + // which may not match the current page if the user has navigated + navigateTo(input.value); }); navigateTo(settings.url); diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index b914e1b8c1a..2ecb96df924 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -182,6 +182,16 @@ function darkenColor(color) { return res; } +function mergeMapping(to, from, property) { + if (from[property]) { + if (to[property]) { + to[property].push(...from[property]); + } else { + to[property] = from[property]; + } + } +} + function getLanguageMappings() { let langMappings = {}; let allExtensions = fs.readdirSync('..'); @@ -204,7 +214,22 @@ function getLanguageMappings() { if (Array.isArray(filenames)) { mapping.fileNames = filenames.map(function (f) { return f.toLowerCase(); }); } - langMappings[languageId] = mapping; + let existing = langMappings[languageId]; + + if (existing) { + // multiple contributions to the same language + // give preference to the contribution wth the configuration + if (languages[k].configuration) { + mergeMapping(mapping, existing, 'extensions'); + mergeMapping(mapping, existing, 'fileNames'); + langMappings[languageId] = mapping; + } else { + mergeMapping(existing, mapping, 'extensions'); + mergeMapping(existing, mapping, 'fileNames'); + } + } else { + langMappings[languageId] = mapping; + } } } } @@ -216,6 +241,8 @@ function getLanguageMappings() { return langMappings; } + + exports.copyFont = function () { return downloadBinary(font, './icons/seti.woff'); }; diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index 3e449273f8c..22d4451a39a 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 663a3361dbb..7acce13639c 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -23,1059 +23,1059 @@ ], "iconDefinitions": { "_R_light": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E001", "fontColor": "#498ba7" }, "_R": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E001", "fontColor": "#519aba" }, "_argdown_light": { - "fontCharacter": "\\E002", + "fontCharacter": "\\E003", "fontColor": "#498ba7" }, "_argdown": { - "fontCharacter": "\\E002", + "fontCharacter": "\\E003", "fontColor": "#519aba" }, "_asm_light": { - "fontCharacter": "\\E003", + "fontCharacter": "\\E004", "fontColor": "#b8383d" }, "_asm": { - "fontCharacter": "\\E003", + "fontCharacter": "\\E004", "fontColor": "#cc3e44" }, "_audio_light": { - "fontCharacter": "\\E004", + "fontCharacter": "\\E005", "fontColor": "#9068b0" }, "_audio": { - "fontCharacter": "\\E004", + "fontCharacter": "\\E005", "fontColor": "#a074c4" }, "_babel_light": { - "fontCharacter": "\\E005", + "fontCharacter": "\\E006", "fontColor": "#b7b73b" }, "_babel": { - "fontCharacter": "\\E005", + "fontCharacter": "\\E006", "fontColor": "#cbcb41" }, "_bower_light": { - "fontCharacter": "\\E006", + "fontCharacter": "\\E007", "fontColor": "#cc6d2e" }, "_bower": { - "fontCharacter": "\\E006", + "fontCharacter": "\\E007", "fontColor": "#e37933" }, "_bsl_light": { - "fontCharacter": "\\E007", + "fontCharacter": "\\E008", "fontColor": "#b8383d" }, "_bsl": { - "fontCharacter": "\\E007", + "fontCharacter": "\\E008", "fontColor": "#cc3e44" }, "_c_light": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#498ba7" }, "_c": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#519aba" }, "_c-sharp_light": { - "fontCharacter": "\\E008", + "fontCharacter": "\\E009", "fontColor": "#498ba7" }, "_c-sharp": { - "fontCharacter": "\\E008", + "fontCharacter": "\\E009", "fontColor": "#519aba" }, "_c_1_light": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#9068b0" }, "_c_1": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#a074c4" }, "_c_2_light": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#b7b73b" }, "_c_2": { - "fontCharacter": "\\E009", + "fontCharacter": "\\E00A", "fontColor": "#cbcb41" }, "_cake_light": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E00B", "fontColor": "#b8383d" }, "_cake": { - "fontCharacter": "\\E00A", + "fontCharacter": "\\E00B", "fontColor": "#cc3e44" }, "_cake_php_light": { - "fontCharacter": "\\E00B", + "fontCharacter": "\\E00C", "fontColor": "#b8383d" }, "_cake_php": { - "fontCharacter": "\\E00B", + "fontCharacter": "\\E00C", "fontColor": "#cc3e44" }, "_clock_light": { - "fontCharacter": "\\E00F", + "fontCharacter": "\\E010", "fontColor": "#498ba7" }, "_clock": { - "fontCharacter": "\\E00F", + "fontCharacter": "\\E010", "fontColor": "#519aba" }, "_clock_1_light": { - "fontCharacter": "\\E00F", + "fontCharacter": "\\E010", "fontColor": "#627379" }, "_clock_1": { - "fontCharacter": "\\E00F", + "fontCharacter": "\\E010", "fontColor": "#6d8086" }, "_clojure_light": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E011", "fontColor": "#7fae42" }, "_clojure": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E011", "fontColor": "#8dc149" }, "_clojure_1_light": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E011", "fontColor": "#498ba7" }, "_clojure_1": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E011", "fontColor": "#519aba" }, "_code-climate_light": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E012", "fontColor": "#7fae42" }, "_code-climate": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E012", "fontColor": "#8dc149" }, "_code-search_light": { - "fontCharacter": "\\E012", + "fontCharacter": "\\E013", "fontColor": "#9068b0" }, "_code-search": { - "fontCharacter": "\\E012", + "fontCharacter": "\\E013", "fontColor": "#a074c4" }, "_coffee_light": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E014", "fontColor": "#b7b73b" }, "_coffee": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E014", "fontColor": "#cbcb41" }, "_coldfusion_light": { - "fontCharacter": "\\E015", + "fontCharacter": "\\E016", "fontColor": "#498ba7" }, "_coldfusion": { - "fontCharacter": "\\E015", + "fontCharacter": "\\E016", "fontColor": "#519aba" }, "_config_light": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E017", "fontColor": "#627379" }, "_config": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E017", "fontColor": "#6d8086" }, "_cpp_light": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#498ba7" }, "_cpp": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#519aba" }, "_cpp_1_light": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#9068b0" }, "_cpp_1": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#a074c4" }, "_cpp_2_light": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#b7b73b" }, "_cpp_2": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E018", "fontColor": "#cbcb41" }, "_crystal_light": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E019", "fontColor": "#bfc2c1" }, "_crystal": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E019", "fontColor": "#d4d7d6" }, "_crystal_embedded_light": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E01A", "fontColor": "#bfc2c1" }, "_crystal_embedded": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E01A", "fontColor": "#d4d7d6" }, "_css_light": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E01B", "fontColor": "#498ba7" }, "_css": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E01B", "fontColor": "#519aba" }, "_csv_light": { - "fontCharacter": "\\E01B", + "fontCharacter": "\\E01C", "fontColor": "#7fae42" }, "_csv": { - "fontCharacter": "\\E01B", + "fontCharacter": "\\E01C", "fontColor": "#8dc149" }, "_cu_light": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01D", "fontColor": "#7fae42" }, "_cu": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01D", "fontColor": "#8dc149" }, "_cu_1_light": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01D", "fontColor": "#9068b0" }, "_cu_1": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01D", "fontColor": "#a074c4" }, "_d_light": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01E", "fontColor": "#b8383d" }, "_d": { - "fontCharacter": "\\E01D", + "fontCharacter": "\\E01E", "fontColor": "#cc3e44" }, "_dart_light": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01F", "fontColor": "#498ba7" }, "_dart": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E01F", "fontColor": "#519aba" }, "_db_light": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E020", "fontColor": "#dd4b78" }, "_db": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E020", "fontColor": "#f55385" }, "_default_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#bfc2c1" }, "_default": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E021", "fontColor": "#d4d7d6" }, "_docker_light": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#498ba7" }, "_docker": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#519aba" }, "_docker_1_light": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#455155" }, "_docker_1": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#4d5a5e" }, "_docker_2_light": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#7fae42" }, "_docker_2": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#8dc149" }, "_docker_3_light": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#dd4b78" }, "_docker_3": { - "fontCharacter": "\\E022", + "fontCharacter": "\\E023", "fontColor": "#f55385" }, "_ejs_light": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E025", "fontColor": "#b7b73b" }, "_ejs": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E025", "fontColor": "#cbcb41" }, "_elixir_light": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E026", "fontColor": "#9068b0" }, "_elixir": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E026", "fontColor": "#a074c4" }, "_elixir_script_light": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E027", "fontColor": "#9068b0" }, "_elixir_script": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E027", "fontColor": "#a074c4" }, "_elm_light": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#498ba7" }, "_elm": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E028", "fontColor": "#519aba" }, "_eslint_light": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#9068b0" }, "_eslint": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#a074c4" }, "_eslint_1_light": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#455155" }, "_eslint_1": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02A", "fontColor": "#4d5a5e" }, "_ethereum_light": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02B", "fontColor": "#498ba7" }, "_ethereum": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02B", "fontColor": "#519aba" }, "_f-sharp_light": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02C", "fontColor": "#498ba7" }, "_f-sharp": { - "fontCharacter": "\\E02B", + "fontCharacter": "\\E02C", "fontColor": "#519aba" }, "_favicon_light": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02D", "fontColor": "#b7b73b" }, "_favicon": { - "fontCharacter": "\\E02C", + "fontCharacter": "\\E02D", "fontColor": "#cbcb41" }, "_firebase_light": { - "fontCharacter": "\\E02D", + "fontCharacter": "\\E02E", "fontColor": "#cc6d2e" }, "_firebase": { - "fontCharacter": "\\E02D", + "fontCharacter": "\\E02E", "fontColor": "#e37933" }, "_firefox_light": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02F", "fontColor": "#cc6d2e" }, "_firefox": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E02F", "fontColor": "#e37933" }, "_font_light": { - "fontCharacter": "\\E030", + "fontCharacter": "\\E031", "fontColor": "#b8383d" }, "_font": { - "fontCharacter": "\\E030", + "fontCharacter": "\\E031", "fontColor": "#cc3e44" }, "_git_light": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E032", "fontColor": "#3b4b52" }, "_git": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E032", "fontColor": "#41535b" }, "_github_light": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E035", "fontColor": "#bfc2c1" }, "_github": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E035", "fontColor": "#d4d7d6" }, "_go_light": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E036", "fontColor": "#498ba7" }, "_go": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E036", "fontColor": "#519aba" }, "_go2_light": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E037", "fontColor": "#498ba7" }, "_go2": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E037", "fontColor": "#519aba" }, "_godot_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#498ba7" }, "_godot": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#519aba" }, "_godot_1_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#b8383d" }, "_godot_1": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#cc3e44" }, "_godot_2_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#b7b73b" }, "_godot_2": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#cbcb41" }, "_godot_3_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#9068b0" }, "_godot_3": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E038", "fontColor": "#a074c4" }, "_gradle_light": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E039", "fontColor": "#498ba7" }, "_gradle": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E039", "fontColor": "#519aba" }, "_grails_light": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E03A", "fontColor": "#7fae42" }, "_grails": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E03A", "fontColor": "#8dc149" }, "_graphql_light": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E03B", "fontColor": "#dd4b78" }, "_graphql": { - "fontCharacter": "\\E03A", + "fontCharacter": "\\E03B", "fontColor": "#f55385" }, "_grunt_light": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03C", "fontColor": "#cc6d2e" }, "_grunt": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E03C", "fontColor": "#e37933" }, "_gulp_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03D", "fontColor": "#b8383d" }, "_gulp": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E03D", "fontColor": "#cc3e44" }, "_haml_light": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E03F", "fontColor": "#b8383d" }, "_haml": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E03F", "fontColor": "#cc3e44" }, "_happenings_light": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E040", "fontColor": "#498ba7" }, "_happenings": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E040", "fontColor": "#519aba" }, "_haskell_light": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E041", "fontColor": "#9068b0" }, "_haskell": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E041", "fontColor": "#a074c4" }, "_haxe_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#cc6d2e" }, "_haxe": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#e37933" }, "_haxe_1_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#b7b73b" }, "_haxe_1": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#cbcb41" }, "_haxe_2_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#498ba7" }, "_haxe_2": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#519aba" }, "_haxe_3_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#9068b0" }, "_haxe_3": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E042", "fontColor": "#a074c4" }, "_heroku_light": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E043", "fontColor": "#9068b0" }, "_heroku": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E043", "fontColor": "#a074c4" }, "_hex_light": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E044", "fontColor": "#b8383d" }, "_hex": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E044", "fontColor": "#cc3e44" }, "_html_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#498ba7" }, "_html": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#519aba" }, "_html_1_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#7fae42" }, "_html_1": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#8dc149" }, "_html_2_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#b7b73b" }, "_html_2": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#cbcb41" }, "_html_3_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#cc6d2e" }, "_html_3": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E045", "fontColor": "#e37933" }, "_html_erb_light": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E046", "fontColor": "#b8383d" }, "_html_erb": { - "fontCharacter": "\\E045", + "fontCharacter": "\\E046", "fontColor": "#cc3e44" }, "_ignored_light": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E047", "fontColor": "#3b4b52" }, "_ignored": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E047", "fontColor": "#41535b" }, "_illustrator_light": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E048", "fontColor": "#b7b73b" }, "_illustrator": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E048", "fontColor": "#cbcb41" }, "_image_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E049", "fontColor": "#9068b0" }, "_image": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E049", "fontColor": "#a074c4" }, "_info_light": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04A", "fontColor": "#498ba7" }, "_info": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04A", "fontColor": "#519aba" }, "_ionic_light": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04B", "fontColor": "#498ba7" }, "_ionic": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04B", "fontColor": "#519aba" }, "_jade_light": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E04C", "fontColor": "#b8383d" }, "_jade": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E04C", "fontColor": "#cc3e44" }, "_java_light": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04D", "fontColor": "#b8383d" }, "_java": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E04D", "fontColor": "#cc3e44" }, "_javascript_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#b7b73b" }, "_javascript": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#cbcb41" }, "_javascript_1_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#cc6d2e" }, "_javascript_1": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#e37933" }, "_javascript_2_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#498ba7" }, "_javascript_2": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E04E", "fontColor": "#519aba" }, "_jenkins_light": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E04F", "fontColor": "#b8383d" }, "_jenkins": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E04F", "fontColor": "#cc3e44" }, "_jinja_light": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E050", "fontColor": "#b8383d" }, "_jinja": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E050", "fontColor": "#cc3e44" }, "_json_light": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E052", "fontColor": "#b7b73b" }, "_json": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E052", "fontColor": "#cbcb41" }, "_json_1_light": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E052", "fontColor": "#7fae42" }, "_json_1": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E052", "fontColor": "#8dc149" }, "_julia_light": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E053", "fontColor": "#9068b0" }, "_julia": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E053", "fontColor": "#a074c4" }, "_karma_light": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E054", "fontColor": "#7fae42" }, "_karma": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E054", "fontColor": "#8dc149" }, "_kotlin_light": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E055", "fontColor": "#cc6d2e" }, "_kotlin": { - "fontCharacter": "\\E054", + "fontCharacter": "\\E055", "fontColor": "#e37933" }, "_less_light": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E056", "fontColor": "#498ba7" }, "_less": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E056", "fontColor": "#519aba" }, "_license_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#b7b73b" }, "_license": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#cbcb41" }, "_license_1_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#cc6d2e" }, "_license_1": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#e37933" }, "_license_2_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#b8383d" }, "_license_2": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E057", "fontColor": "#cc3e44" }, "_liquid_light": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E058", "fontColor": "#7fae42" }, "_liquid": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E058", "fontColor": "#8dc149" }, "_livescript_light": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E059", "fontColor": "#498ba7" }, "_livescript": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E059", "fontColor": "#519aba" }, "_lock_light": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05A", "fontColor": "#7fae42" }, "_lock": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05A", "fontColor": "#8dc149" }, "_lua_light": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E05B", "fontColor": "#498ba7" }, "_lua": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E05B", "fontColor": "#519aba" }, "_makefile_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#cc6d2e" }, "_makefile": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#e37933" }, "_makefile_1_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#9068b0" }, "_makefile_1": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#a074c4" }, "_makefile_2_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#627379" }, "_makefile_2": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#6d8086" }, "_makefile_3_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#498ba7" }, "_makefile_3": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E05C", "fontColor": "#519aba" }, "_markdown_light": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05D", "fontColor": "#498ba7" }, "_markdown": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E05D", "fontColor": "#519aba" }, "_maven_light": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E05E", "fontColor": "#b8383d" }, "_maven": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E05E", "fontColor": "#cc3e44" }, "_mdo_light": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E05F", "fontColor": "#b8383d" }, "_mdo": { - "fontCharacter": "\\E05E", + "fontCharacter": "\\E05F", "fontColor": "#cc3e44" }, "_mustache_light": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E060", "fontColor": "#cc6d2e" }, "_mustache": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E060", "fontColor": "#e37933" }, "_nim_light": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E062", "fontColor": "#b7b73b" }, "_nim": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E062", "fontColor": "#cbcb41" }, "_notebook_light": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E063", "fontColor": "#498ba7" }, "_notebook": { - "fontCharacter": "\\E062", + "fontCharacter": "\\E063", "fontColor": "#519aba" }, "_npm_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E064", "fontColor": "#3b4b52" }, "_npm": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E064", "fontColor": "#41535b" }, "_npm_1_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E064", "fontColor": "#b8383d" }, "_npm_1": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E064", "fontColor": "#cc3e44" }, "_npm_ignored_light": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E065", "fontColor": "#3b4b52" }, "_npm_ignored": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E065", "fontColor": "#41535b" }, "_nunjucks_light": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E066", "fontColor": "#7fae42" }, "_nunjucks": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E066", "fontColor": "#8dc149" }, "_ocaml_light": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E067", "fontColor": "#cc6d2e" }, "_ocaml": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E067", "fontColor": "#e37933" }, "_odata_light": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E068", "fontColor": "#cc6d2e" }, "_odata": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E068", "fontColor": "#e37933" }, "_pddl_light": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E069", "fontColor": "#9068b0" }, "_pddl": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E069", "fontColor": "#a074c4" }, "_pdf_light": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06A", "fontColor": "#b8383d" }, "_pdf": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06A", "fontColor": "#cc3e44" }, "_perl_light": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E06B", "fontColor": "#498ba7" }, "_perl": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E06B", "fontColor": "#519aba" }, "_photoshop_light": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E06C", "fontColor": "#498ba7" }, "_photoshop": { - "fontCharacter": "\\E06B", + "fontCharacter": "\\E06C", "fontColor": "#519aba" }, "_php_light": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E06D", "fontColor": "#9068b0" }, "_php": { - "fontCharacter": "\\E06C", + "fontCharacter": "\\E06D", "fontColor": "#a074c4" }, "_plan_light": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E06E", "fontColor": "#7fae42" }, "_plan": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E06E", "fontColor": "#8dc149" }, "_platformio_light": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E06F", "fontColor": "#cc6d2e" }, "_platformio": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E06F", "fontColor": "#e37933" }, "_powershell_light": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E070", "fontColor": "#498ba7" }, "_powershell": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E070", "fontColor": "#519aba" }, "_prisma_light": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E071", "fontColor": "#498ba7" }, "_prisma": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E071", "fontColor": "#519aba" }, "_prolog_light": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E073", "fontColor": "#cc6d2e" }, "_prolog": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E073", "fontColor": "#e37933" }, "_pug_light": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E074", "fontColor": "#b8383d" }, "_pug": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E074", "fontColor": "#cc3e44" }, "_puppet_light": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E075", "fontColor": "#b7b73b" }, "_puppet": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E075", "fontColor": "#cbcb41" }, "_python_light": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E076", "fontColor": "#498ba7" }, "_python": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E076", "fontColor": "#519aba" }, "_react_light": { @@ -1502,7 +1502,6 @@ "exs": "_elixir_script", "elm": "_elm", "ico": "_favicon", - "gitignore": "_git", "gitconfig": "_git", "gitkeep": "_git", "gitattributes": "_git", @@ -1528,7 +1527,6 @@ "jade": "_jade", "class": "_java", "classpath": "_java", - "properties": "_java", "js.map": "_javascript", "spec.js": "_javascript_1", "test.js": "_javascript_1", @@ -1557,6 +1555,8 @@ "njs": "_nunjucks", "nj": "_nunjucks", "npm-debug.log": "_npm", + "npmignore": "_npm_1", + "npmrc": "_npm_1", "ml": "_ocaml", "mli": "_ocaml", "cmx": "_ocaml", @@ -1652,11 +1652,9 @@ "bowerrc": "_bower", "dockerignore": "_docker_1", "codeclimate.yml": "_code-climate", - "eslintrc": "_eslint", "eslintrc.js": "_eslint", "eslintrc.yaml": "_eslint", "eslintrc.yml": "_eslint", - "eslintrc.json": "_eslint", "eslintignore": "_eslint_1", "firebaserc": "_firebase", "jshintrc": "_javascript_2", @@ -1689,6 +1687,7 @@ "version.md": "_clock", "version": "_clock", "mvnw": "_maven", + "tsconfig.json": "_tsconfig", "swagger.json": "_json_1", "swagger.yml": "_json_1", "swagger.yaml": "_json_1", @@ -1745,13 +1744,13 @@ "csharp": "_c-sharp", "css": "_css", "dockerfile": "_docker", - "ignore": "_npm_1", + "ignore": "_git", "fsharp": "_f-sharp", "go": "_go2", "groovy": "_grails", "handlebars": "_mustache", "html": "_html_3", - "properties": "_npm_1", + "properties": "_java", "java": "_java", "javascriptreact": "_react", "javascript": "_javascript", @@ -1850,7 +1849,6 @@ "exs": "_elixir_script_light", "elm": "_elm_light", "ico": "_favicon_light", - "gitignore": "_git_light", "gitconfig": "_git_light", "gitkeep": "_git_light", "gitattributes": "_git_light", @@ -1876,7 +1874,6 @@ "jade": "_jade_light", "class": "_java_light", "classpath": "_java_light", - "properties": "_java_light", "js.map": "_javascript_light", "spec.js": "_javascript_1_light", "test.js": "_javascript_1_light", @@ -1905,6 +1902,8 @@ "njs": "_nunjucks_light", "nj": "_nunjucks_light", "npm-debug.log": "_npm_light", + "npmignore": "_npm_1_light", + "npmrc": "_npm_1_light", "ml": "_ocaml_light", "mli": "_ocaml_light", "cmx": "_ocaml_light", @@ -2000,11 +1999,9 @@ "bowerrc": "_bower_light", "dockerignore": "_docker_1_light", "codeclimate.yml": "_code-climate_light", - "eslintrc": "_eslint_light", "eslintrc.js": "_eslint_light", "eslintrc.yaml": "_eslint_light", "eslintrc.yml": "_eslint_light", - "eslintrc.json": "_eslint_light", "eslintignore": "_eslint_1_light", "firebaserc": "_firebase_light", "jshintrc": "_javascript_2_light", @@ -2036,13 +2033,13 @@ "csharp": "_c-sharp_light", "css": "_css_light", "dockerfile": "_docker_light", - "ignore": "_npm_1_light", + "ignore": "_git_light", "fsharp": "_f-sharp_light", "go": "_go2_light", "groovy": "_grails_light", "handlebars": "_mustache_light", "html": "_html_3_light", - "properties": "_npm_1_light", + "properties": "_java_light", "java": "_java_light", "javascriptreact": "_react_light", "javascript": "_javascript_light", @@ -2106,6 +2103,7 @@ "version.md": "_clock_light", "version": "_clock_light", "mvnw": "_maven_light", + "tsconfig.json": "_tsconfig_light", "swagger.json": "_json_1_light", "swagger.yml": "_json_1_light", "swagger.yaml": "_json_1_light", diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index d5844e1af8e..d2b72e46ba8 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -9,7 +9,9 @@ "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "enableProposedApi": true, - "requiresWorkspaceTrust": "onDemand", + "workspaceTrust": { + "required": "onDemand" + }, "engines": { "vscode": "^1.30.0" }, @@ -19,13 +21,12 @@ "dependencies": { "jsonc-parser": "^2.2.1", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.14", + "typescript-vscode-sh-plugin": "^0.7.1", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { "@types/node": "^12.19.9", - "@types/rimraf": "^2.0.4", "@types/semver": "^5.5.0" }, "scripts": { diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index f64c0b27553..4284366e273 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -335,7 +335,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider { + async _provideSemanticTokens(document: vscode.TextDocument, requestArg: Proto.EncodedSemanticClassificationsRequestArgs, token: vscode.CancellationToken): Promise { const file = this.client.toOpenedFilePath(document); if (!file) { return null; @@ -75,6 +74,8 @@ class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensPro const versionBeforeRequest = document.version; + requestArg.format = '2020'; + const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token, { cancelOnResourceChange: document.uri }); @@ -148,9 +149,42 @@ function waitForDocumentChangesToEnd(document: vscode.TextDocument) { } -// typescript-vscode-sh-plugin encodes type and modifiers in the classification: +// typescript encodes type and modifiers in the classification: // TSClassification = (TokenType + 1) << 8 + TokenModifier +declare const enum TokenType { + class = 0, + enum = 1, + interface = 2, + namespace = 3, + typeParameter = 4, + type = 5, + parameter = 6, + variable = 7, + enumMember = 8, + property = 9, + function = 10, + method = 11, + _ = 12 +} +declare const enum TokenModifier { + declaration = 0, + static = 1, + async = 2, + readonly = 3, + defaultLibrary = 4, + local = 5, + _ = 6 +} +declare const enum TokenEncodingConsts { + typeOffset = 8, + modifierMask = 255 +} +declare const enum VersionRequirement { + major = 3, + minor = 7 +} + function getTokenTypeFromClassification(tsClassification: number): number | undefined { if (tsClassification > TokenEncodingConsts.modifierMask) { return (tsClassification >> TokenEncodingConsts.typeOffset) - 1; @@ -174,7 +208,7 @@ tokenTypes[TokenType.variable] = 'variable'; tokenTypes[TokenType.enumMember] = 'enumMember'; tokenTypes[TokenType.property] = 'property'; tokenTypes[TokenType.function] = 'function'; -tokenTypes[TokenType.member] = 'method'; +tokenTypes[TokenType.method] = 'method'; const tokenModifiers: string[] = []; tokenModifiers[TokenModifier.async] = 'async'; @@ -184,14 +218,6 @@ tokenModifiers[TokenModifier.static] = 'static'; tokenModifiers[TokenModifier.local] = 'local'; tokenModifiers[TokenModifier.defaultLibrary] = 'defaultLibrary'; -// make sure token types and modifiers are complete -if (tokenTypes.filter(t => !!t).length !== TokenType._) { - console.warn('typescript-vscode-sh-plugin has added new tokens types.'); -} -if (tokenModifiers.filter(t => !!t).length !== TokenModifier._) { - console.warn('typescript-vscode-sh-plugin has added new tokens modifiers.'); -} - // mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) const tokenTypeMap: number[] = []; tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class; diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index f95020cdbe7..f6a849aa15a 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,37 +2,11 @@ # yarn lockfile v1 -"@types/glob@*": - version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" - integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== - -"@types/node@*": - version "14.11.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.2.tgz#2de1ed6670439387da1c9f549a2ade2b0a799256" - integrity sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA== - "@types/node@^12.19.9": version "12.19.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679" integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q== -"@types/rimraf@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.4.tgz#403887b0b53c6100a6c35d2ab24f6ccc042fec46" - integrity sha512-8gBudvllD2A/c0CcEX/BivIDorHFt5UI5m46TsNj8DjWCCTTZT74kEe4g+QsY7P/B9WdO98d82zZgXO/RQzu2Q== - dependencies: - "@types/glob" "*" - "@types/node" "*" - "@types/semver@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" @@ -74,10 +48,10 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -typescript-vscode-sh-plugin@^0.6.14: - version "0.6.14" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.14.tgz#a81031b502f6346a26ea49ce082438c3e353bb38" - integrity sha512-AkNlRBbI6K7gk29O92qthNSvc6jjmNQ6isVXoYxkFwPa8D04tIv2SOPd+sd+mNpso4tNdL2gy7nVtrd5yFqvlA== +typescript-vscode-sh-plugin@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.7.1.tgz#0be744032aa7fdcb764497342fd0fd2d093ed7ca" + integrity sha512-upXxgeI21tEMJxRVxF4PekQBM+eF0cRp0GgD2sblQkEm43NR+Fcbusa+FKAekUgwkbjVq1Loq+vKCCUbxM6R8A== vscode-extension-telemetry@0.1.1: version "0.1.1" diff --git a/extensions/vscode-api-tests/.gitignore b/extensions/vscode-api-tests/.gitignore index 8e5962ee727..19685d0f18e 100644 --- a/extensions/vscode-api-tests/.gitignore +++ b/extensions/vscode-api-tests/.gitignore @@ -1,2 +1,3 @@ out -node_modules \ No newline at end of file +node_modules +testWorkspace/.vscode/tasks.json diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 7e576805d5c..6d39b43c989 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -5,7 +5,9 @@ "publisher": "vscode", "license": "MIT", "enableProposedApi": true, - "requiresWorkspaceTrust": "onDemand", + "workspaceTrust": { + "required": "onDemand" + }, "private": true, "activationEvents": [], "main": "./out/extension", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 9fccde765f9..77ac6734b16 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -67,4 +67,12 @@ suite('Notebook Editor', function () { assert.strictEqual(count1 + 1, count2); }); + + test.skip('Opening a notebook should fire activeNotebook event changed only once', async function () { + const openedEditor = utils.asPromise(vscode.window.onDidChangeActiveNotebookEditor); + const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); + const editor = await vscode.window.showNotebookDocument(resource); + assert.ok(await openedEditor); + assert.strictEqual(editor.document.uri.toString(), resource.toString()); + }); }); 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 517033df83e..027370243b0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -286,28 +286,9 @@ suite('Notebook API tests', function () { ] }); - const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; - const moveCellEvent = asPromise(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]] - } - ] - }); + await moveCellEvent; const cellOutputChange = asPromise(vscode.notebook.onDidChangeCellOutputs); await vscode.commands.executeCommand('notebook.cell.execute'); @@ -350,25 +331,7 @@ suite('Notebook API tests', function () { assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); const moveChange = asPromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.moveDown'); - const ret = await moveChange; - assert.deepStrictEqual(ret, { - document: vscode.window.activeNotebookEditor?.document, - changes: [ - { - start: 0, - deletedCount: 1, - deletedItems: [activeCell], - items: [] - }, - { - start: 1, - deletedCount: 0, - deletedItems: [], - items: [activeCell] - } - ] - }); - + await moveChange; await saveAllEditors(); await closeAllEditors(); @@ -931,7 +894,7 @@ suite('Notebook API tests', function () { await saveAllFilesAndCloseAll(secondResource); }); - test('multiple tabs: different editors with same document', async function () { + test.skip('multiple tabs: different editors with same document', async function () { const resource = await createRandomFile('', undefined, '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstNotebookEditor = vscode.window.activeNotebookEditor; @@ -1169,12 +1132,8 @@ suite('Notebook API tests', function () { assert.strictEqual(document.cells[0].metadata.executionOrder, executionOrder); assert.strictEqual(document.cells[0].metadata.runState, vscode.NotebookCellRunState.Success); }); - test('Opening a notebook should fire activeNotebook event changed only once', async function () { - const openedEditor = asPromise(vscode.window.onDidChangeActiveNotebookEditor); - const resource = await createRandomFile('', undefined, '.vsctestnb'); - await vscode.notebook.openNotebookDocument(resource); - assert.ok(await openedEditor); - }); + + // }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts index 5ec438a1714..8d3c89507d6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.tasks.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event, workspace, ConfigurationTarget } from 'vscode'; +import { window, tasks, Disposable, TaskDefinition, Task, EventEmitter, CustomExecution, Pseudoterminal, TaskScope, commands, env, UIKind, ShellExecution, TaskExecution, Terminal, Event, workspace, ConfigurationTarget, TaskProcessStartEvent } from 'vscode'; import { assertNoRpc } from '../utils'; // Disable tasks tests: @@ -32,245 +32,300 @@ import { assertNoRpc } from '../utils'; disposables.length = 0; }); - test('CustomExecution task should start and shutdown successfully', async () => { - interface CustomTestingTaskDefinition extends TaskDefinition { - /** - * One of the task properties. This can be used to customize the task in the tasks.json - */ - customProp1: string; - } - const taskType: string = 'customTesting'; - const taskName = 'First custom task'; - let isPseudoterminalClosed = false; - // There's a strict order that should be observed here: - // 1. The terminal opens - // 2. The terminal is written to. - // 3. The terminal is closed. - enum TestOrder { - Start, - TerminalOpened, - TerminalWritten, - TerminalClosed - } - - let testOrder = TestOrder.Start; - - // Launch the task - const terminal = await new Promise(r => { - disposables.push(window.onDidOpenTerminal(e => { - assert.strictEqual(testOrder, TestOrder.Start); - testOrder = TestOrder.TerminalOpened; - r(e); - })); - disposables.push(tasks.registerTaskProvider(taskType, { - provideTasks: () => { - const result: Task[] = []; - const kind: CustomTestingTaskDefinition = { - type: taskType, - customProp1: 'testing task one' - }; - const writeEmitter = new EventEmitter(); - const execution = new CustomExecution((): Thenable => { - const pty: Pseudoterminal = { - onDidWrite: writeEmitter.event, - open: () => writeEmitter.fire('testing\r\n'), - close: () => isPseudoterminalClosed = true - }; - return Promise.resolve(pty); + suite('ShellExecution', () => { + test('Execution from onDidEndTaskProcess and onDidStartTaskProcess are equal to original', () => { + return new Promise(async (resolve) => { + const task = new Task({ type: 'testTask' }, TaskScope.Workspace, 'echo', 'testTask', new ShellExecution('echo', ['hello test'])); + let taskExecution: TaskExecution | undefined; + const executeDoneEvent: EventEmitter = new EventEmitter(); + const taskExecutionShouldBeSet: Promise = new Promise(resolve => { + const disposable = executeDoneEvent.event(() => { + resolve(); + disposable.dispose(); }); - const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution); - result.push(task); - return result; + }); + let count = 2; + const progressMade: EventEmitter = new EventEmitter(); + let startSucceeded = false; + let endSucceeded = false; + disposables.push(progressMade.event(() => { + count--; + if ((count === 0) && startSucceeded && endSucceeded) { + resolve(); + } + })); + + + disposables.push(tasks.onDidStartTaskProcess(async (e) => { + await taskExecutionShouldBeSet; + if (e.execution === taskExecution) { + startSucceeded = true; + progressMade.fire(); + } + })); + + disposables.push(tasks.onDidEndTaskProcess(async (e) => { + await taskExecutionShouldBeSet; + if (e.execution === taskExecution) { + endSucceeded = true; + progressMade.fire(); + } + })); + taskExecution = await tasks.executeTask(task); + executeDoneEvent.fire(); + }); + }); + + test('dependsOn task should start with a different processId (#118256)', async () => { + // Set up dependsOn task by creating tasks.json since this is not possible via the API + // Tasks API + const tasksConfig = workspace.getConfiguration('tasks'); + await tasksConfig.update('version', '2.0.0', ConfigurationTarget.Workspace); + await tasksConfig.update('tasks', [ + { + label: 'taskToDependOn', + type: 'shell', + command: 'sleep 1', + problemMatcher: [] }, - resolveTask(_task: Task): Task | undefined { - assert.fail('resolveTask should not trigger during the test'); + { + label: 'Run this task', + type: 'shell', + command: 'sleep 1', + problemMatcher: [], + dependsOn: 'taskToDependOn' } - })); - commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); - }); + ], ConfigurationTarget.Workspace); - // Verify the output - await new Promise(r => { - disposables.push(window.onDidWriteTerminalData(e => { - if (e.terminal !== terminal) { - return; - } - assert.strictEqual(testOrder, TestOrder.TerminalOpened); - testOrder = TestOrder.TerminalWritten; - assert.notStrictEqual(terminal, undefined); - assert.strictEqual(e.data, 'testing\r\n'); - r(); - })); - }); + // Run the task + commands.executeCommand('workbench.action.tasks.runTask', 'Run this task'); - // Dispose the terminal - await new Promise(r => { - disposables.push(window.onDidCloseTerminal(() => { - assert.strictEqual(testOrder, TestOrder.TerminalWritten); - testOrder = TestOrder.TerminalClosed; - // Pseudoterminal.close should have fired by now, additionally we want - // to make sure all events are flushed before continuing with more tests - assert.ok(isPseudoterminalClosed); - r(); - })); - terminal.dispose(); - }); - }); - - test('sync CustomExecution task should flush all data on close', async () => { - interface CustomTestingTaskDefinition extends TaskDefinition { - /** - * One of the task properties. This can be used to customize the task in the tasks.json - */ - customProp1: string; - } - const taskType: string = 'customTesting'; - const taskName = 'First custom task'; - - // Launch the task - const terminal = await new Promise(r => { - disposables.push(window.onDidOpenTerminal(e => r(e))); - disposables.push(tasks.registerTaskProvider(taskType, { - provideTasks: () => { - const result: Task[] = []; - const kind: CustomTestingTaskDefinition = { - type: taskType, - customProp1: 'testing task one' - }; - const writeEmitter = new EventEmitter(); - const closeEmitter = new EventEmitter(); - const execution = new CustomExecution((): Thenable => { - const pty: Pseudoterminal = { - onDidWrite: writeEmitter.event, - onDidClose: closeEmitter.event, - open: () => { - writeEmitter.fire('exiting'); - closeEmitter.fire(); - }, - close: () => { } - }; - return Promise.resolve(pty); - }); - const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution); - result.push(task); - return result; - }, - resolveTask(_task: Task): Task | undefined { - assert.fail('resolveTask should not trigger during the test'); - } - })); - commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); - }); - - // Verify the output - await new Promise(r => { - disposables.push(window.onDidWriteTerminalData(e => { - if (e.terminal !== terminal) { - return; - } - assert.strictEqual(e.data, 'exiting'); - r(); - })); - }); - - // Dispose the terminal - await new Promise(r => { - disposables.push(window.onDidCloseTerminal(() => r())); - terminal.dispose(); - }); - }); - - test('Execution from onDidEndTaskProcess and onDidStartTaskProcess are equal to original', () => { - return new Promise(async (resolve) => { - const task = new Task({ type: 'testTask' }, TaskScope.Workspace, 'echo', 'testTask', new ShellExecution('echo', ['hello test'])); - let taskExecution: TaskExecution | undefined; - const executeDoneEvent: EventEmitter = new EventEmitter(); - const taskExecutionShouldBeSet: Promise = new Promise(resolve => { - const disposable = executeDoneEvent.event(() => { - resolve(); - disposable.dispose(); + // Listen for first task and verify valid process ID + const startEvent1 = await new Promise(r => { + const listener = tasks.onDidStartTaskProcess(async (e) => { + if (e.execution.task.name === 'taskToDependOn') { + listener.dispose(); + r(e); + } }); }); - let count = 2; - const progressMade: EventEmitter = new EventEmitter(); - let startSucceeded = false; - let endSucceeded = false; - disposables.push(progressMade.event(() => { - count--; - if ((count === 0) && startSucceeded && endSucceeded) { - resolve(); - } - })); + assert.ok(startEvent1.processId); + // Listen for second task, verify valid process ID and that it's not the process ID of + // the first task + const startEvent2 = await new Promise(r => { + const listener = tasks.onDidStartTaskProcess(async (e) => { + if (e.execution.task.name === 'Run this task') { + listener.dispose(); + r(e); + } + }); + }); + assert.ok(startEvent2.processId); + assert.notStrictEqual(startEvent1.processId, startEvent2.processId); - disposables.push(tasks.onDidStartTaskProcess(async (e) => { - await taskExecutionShouldBeSet; - if (e.execution === taskExecution) { - startSucceeded = true; - progressMade.fire(); - } - })); - - disposables.push(tasks.onDidEndTaskProcess(async (e) => { - await taskExecutionShouldBeSet; - if (e.execution === taskExecution) { - endSucceeded = true; - progressMade.fire(); - } - })); - taskExecution = await tasks.executeTask(task); - executeDoneEvent.fire(); + // Clear out tasks config + await tasksConfig.update('tasks', []); }); }); - // https://github.com/microsoft/vscode/issues/100577 - test('A CustomExecution task can be fetched and executed', () => { - return new Promise(async (resolve, reject) => { - class CustomTerminal implements Pseudoterminal { - private readonly writeEmitter = new EventEmitter(); - public readonly onDidWrite: Event = this.writeEmitter.event; - public async close(): Promise { } - private closeEmitter = new EventEmitter(); - onDidClose: Event = this.closeEmitter.event; - public open(): void { - this.closeEmitter.fire(); - resolve(); - } + suite('CustomExecution', () => { + test('task should start and shutdown successfully', async () => { + interface CustomTestingTaskDefinition extends TaskDefinition { + /** + * One of the task properties. This can be used to customize the task in the tasks.json + */ + customProp1: string; + } + const taskType: string = 'customTesting'; + const taskName = 'First custom task'; + let isPseudoterminalClosed = false; + // There's a strict order that should be observed here: + // 1. The terminal opens + // 2. The terminal is written to. + // 3. The terminal is closed. + enum TestOrder { + Start, + TerminalOpened, + TerminalWritten, + TerminalClosed } - function buildTask(): Task { - const task = new Task( - { - type: 'customTesting', + let testOrder = TestOrder.Start; + + // Launch the task + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(e => { + assert.strictEqual(testOrder, TestOrder.Start); + testOrder = TestOrder.TerminalOpened; + r(e); + })); + disposables.push(tasks.registerTaskProvider(taskType, { + provideTasks: () => { + const result: Task[] = []; + const kind: CustomTestingTaskDefinition = { + type: taskType, + customProp1: 'testing task one' + }; + const writeEmitter = new EventEmitter(); + const execution = new CustomExecution((): Thenable => { + const pty: Pseudoterminal = { + onDidWrite: writeEmitter.event, + open: () => writeEmitter.fire('testing\r\n'), + close: () => isPseudoterminalClosed = true + }; + return Promise.resolve(pty); + }); + const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution); + result.push(task); + return result; }, - TaskScope.Workspace, - 'Test Task', - 'customTesting', - new CustomExecution( - async (): Promise => { - return new CustomTerminal(); - } - ) - ); - return task; - } + resolveTask(_task: Task): Task | undefined { + assert.fail('resolveTask should not trigger during the test'); + } + })); + commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); + }); - disposables.push(tasks.registerTaskProvider('customTesting', { - provideTasks: () => { - return [buildTask()]; - }, - resolveTask(_task: Task): undefined { - return undefined; + // Verify the output + await new Promise(r => { + disposables.push(window.onDidWriteTerminalData(e => { + if (e.terminal !== terminal) { + return; + } + assert.strictEqual(testOrder, TestOrder.TerminalOpened); + testOrder = TestOrder.TerminalWritten; + assert.notStrictEqual(terminal, undefined); + assert.strictEqual(e.data, 'testing\r\n'); + r(); + })); + }); + + // Dispose the terminal + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(() => { + assert.strictEqual(testOrder, TestOrder.TerminalWritten); + testOrder = TestOrder.TerminalClosed; + // Pseudoterminal.close should have fired by now, additionally we want + // to make sure all events are flushed before continuing with more tests + assert.ok(isPseudoterminalClosed); + r(); + })); + terminal.dispose(); + }); + }); + + test('sync task should flush all data on close', async () => { + interface CustomTestingTaskDefinition extends TaskDefinition { + /** + * One of the task properties. This can be used to customize the task in the tasks.json + */ + customProp1: string; + } + const taskType: string = 'customTesting'; + const taskName = 'First custom task'; + + // Launch the task + const terminal = await new Promise(r => { + disposables.push(window.onDidOpenTerminal(e => r(e))); + disposables.push(tasks.registerTaskProvider(taskType, { + provideTasks: () => { + const result: Task[] = []; + const kind: CustomTestingTaskDefinition = { + type: taskType, + customProp1: 'testing task one' + }; + const writeEmitter = new EventEmitter(); + const closeEmitter = new EventEmitter(); + const execution = new CustomExecution((): Thenable => { + const pty: Pseudoterminal = { + onDidWrite: writeEmitter.event, + onDidClose: closeEmitter.event, + open: () => { + writeEmitter.fire('exiting'); + closeEmitter.fire(); + }, + close: () => { } + }; + return Promise.resolve(pty); + }); + const task = new Task(kind, TaskScope.Workspace, taskName, taskType, execution); + result.push(task); + return result; + }, + resolveTask(_task: Task): Task | undefined { + assert.fail('resolveTask should not trigger during the test'); + } + })); + commands.executeCommand('workbench.action.tasks.runTask', `${taskType}: ${taskName}`); + }); + + // Verify the output + await new Promise(r => { + disposables.push(window.onDidWriteTerminalData(e => { + if (e.terminal !== terminal) { + return; + } + assert.strictEqual(e.data, 'exiting'); + r(); + })); + }); + + // Dispose the terminal + await new Promise(r => { + disposables.push(window.onDidCloseTerminal(() => r())); + terminal.dispose(); + }); + }); + + test('A task can be fetched and executed (#100577)', () => { + return new Promise(async (resolve, reject) => { + class CustomTerminal implements Pseudoterminal { + private readonly writeEmitter = new EventEmitter(); + public readonly onDidWrite: Event = this.writeEmitter.event; + public async close(): Promise { } + private closeEmitter = new EventEmitter(); + onDidClose: Event = this.closeEmitter.event; + public open(): void { + this.closeEmitter.fire(); + resolve(); + } } - })); - const task = await tasks.fetchTasks({ type: 'customTesting' }); + function buildTask(): Task { + const task = new Task( + { + type: 'customTesting', + }, + TaskScope.Workspace, + 'Test Task', + 'customTesting', + new CustomExecution( + async (): Promise => { + return new CustomTerminal(); + } + ) + ); + return task; + } - if (task && task.length > 0) { - await tasks.executeTask(task[0]); - } else { - reject('fetched task can\'t be undefined'); - } + disposables.push(tasks.registerTaskProvider('customTesting', { + provideTasks: () => { + return [buildTask()]; + }, + resolveTask(_task: Task): undefined { + return undefined; + } + })); + + const task = await tasks.fetchTasks({ type: 'customTesting' }); + + if (task && task.length > 0) { + await tasks.executeTask(task[0]); + } else { + reject('fetched task can\'t be undefined'); + } + }); }); }); }); diff --git a/package.json b/package.json index 0b2f83aa1fb..22a7b5200e1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.55.0", - "distro": "89f1579ed6a94b2dd7db7a8c29dddd48065cadc8", + "distro": "ba61d25cf002e9a939ea098bba278e965df7a6e7", "author": { "name": "Microsoft Corporation" }, @@ -78,7 +78,7 @@ "tas-client-umd": "0.1.2", "v8-inspect-profiler": "^0.0.20", "vscode-oniguruma": "1.3.1", - "vscode-proxy-agent": "^0.6.0", + "vscode-proxy-agent": "^0.7.0", "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.11.1", "vscode-sqlite3": "4.0.10", @@ -223,4 +223,4 @@ "elliptic": "^6.5.3", "nwmatcher": "^1.4.4" } -} \ No newline at end of file +} diff --git a/product.json b/product.json index cb80575f41f..8ff21c76fb5 100644 --- a/product.json +++ b/product.json @@ -26,7 +26,9 @@ "ms-vscode.vscode-js-profile-flame", "ms-vscode.vscode-js-profile-table", "ms-vscode.github-browser", - "ms-vscode.github-richnav" + "ms-vscode.github-richnav", + "ms-vscode.remotehub", + "ms-vscode.remotehub-insiders" ], "builtInExtensions": [ { diff --git a/remote/package.json b/remote/package.json index 7722334fce2..0c0e4c62a31 100644 --- a/remote/package.json +++ b/remote/package.json @@ -18,7 +18,7 @@ "spdlog": "^0.11.1", "tas-client-umd": "0.1.2", "vscode-oniguruma": "1.3.1", - "vscode-proxy-agent": "^0.6.0", + "vscode-proxy-agent": "^0.7.0", "vscode-regexpp": "^3.1.0", "vscode-ripgrep": "^1.11.1", "vscode-textmate": "5.2.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index ef6d571818e..ea2f9930fcf 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -376,10 +376,10 @@ vscode-oniguruma@1.3.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f" integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ== -vscode-proxy-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.6.0.tgz#3ea5c3c82f7abe945d690eb34b2c877cf5833f12" - integrity sha512-dvoLmEO/IxkbcNrRHH6ey8ITfvau4wDg01S+iAJ5Pq/FoAl2ZeE4cK5VEnQ2JHqM20kTLhyZfkjdHq6l7/T+xA== +vscode-proxy-agent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.7.0.tgz#adf19dfbc9beba508b1c72a83cd64b5d1cb9d84c" + integrity sha512-eXGwlzwQJJqutYHjhC0UfBw1Sp8p2UygXACQjMSMKkIufeF68c5Z12s7v7UVBkqW3KfgEh/zDaO/bPkWLI/RZw== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0" diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index c440312f331..9c49ab29457 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -36,9 +36,27 @@ if [ "@@NAME@@" != "code-oss" ]; then eval $(apt-config shell APT_TRUSTED_PARTS Dir::Etc::trustedparts/d) CODE_TRUSTED_PART=${APT_TRUSTED_PARTS}microsoft.gpg - # Sourced from https://packages.microsoft.com/keys/microsoft.asc - if [ ! -f $CODE_TRUSTED_PART ]; then - echo "-----BEGIN PGP PUBLIC KEY BLOCK----- + # Install repository source list + WRITE_SOURCE=0 + if [ ! -f $CODE_SOURCE_PART ] && [ ! -f /etc/rpi-issue ]; then + # Write source list if it does not exist and we're not running on Raspberry Pi OS + WRITE_SOURCE=1 + elif grep -Eq "http:\/\/packages\.microsoft\.com\/repos\/vscode" $CODE_SOURCE_PART; then + # Migrate from old repository + WRITE_SOURCE=1 + elif grep -q "# disabled on upgrade to" $CODE_SOURCE_PART; then + # Write source list if it was disabled by OS upgrade + WRITE_SOURCE=1 + fi + + if [ "$WRITE_SOURCE" -eq "1" ]; then + echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### +# You may comment out this entry, but any other modifications may be lost. +deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART + + # Sourced from https://packages.microsoft.com/keys/microsoft.asc + if [ ! -f $CODE_TRUSTED_PART ]; then + echo "-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.7 (GNU/Linux) mQENBFYxWIwBCADAKoZhZlJxGNGWzqV+1OG1xiQeoowKhssGAKvd+buXCGISZJwT @@ -57,25 +75,7 @@ XdNgj4Jd2/g6T9InmWT0hASljur+dJnzNiNCkbn9KbX7J/qK1IbR8y560yRmFsU+ NdCFTW7wY0Fb1fWJ+/KTsC4= =J6gs -----END PGP PUBLIC KEY BLOCK----- -" | gpg --dearmor > microsoft.gpg - mv microsoft.gpg $CODE_TRUSTED_PART - fi - - # Install repository source list - WRITE_SOURCE=0 - if [ ! -f $CODE_SOURCE_PART ]; then - # Write source list if it does not exist - WRITE_SOURCE=1 - elif grep -Eq "http:\/\/packages\.microsoft\.com\/repos\/vscode" $CODE_SOURCE_PART; then - # Migrate from old repository - WRITE_SOURCE=1 - elif grep -q "# disabled on upgrade to" $CODE_SOURCE_PART; then - # Write source list if it was disabled by OS upgrade - WRITE_SOURCE=1 - fi - if [ "$WRITE_SOURCE" -eq "1" ]; then - echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### -# You may comment out this entry, but any other modifications may be lost. -deb [arch=amd64,arm64,armhf] http://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART +" | gpg --dearmor > $CODE_TRUSTED_PART + fi fi fi diff --git a/scripts/code-cli.bat b/scripts/code-cli.bat index 2e4e34ff32e..50c85a55143 100644 --- a/scripts/code-cli.bat +++ b/scripts/code-cli.bat @@ -14,7 +14,7 @@ set NAMESHORT=%NAMESHORT:"=%.exe set CODE=".build\electron\%NAMESHORT%" :: Manage built-in extensions -if "%1"=="--builtin" goto builtin +if "%~1"=="--builtin" goto builtin :: Configuration set ELECTRON_RUN_AS_NODE=1 diff --git a/scripts/code.bat b/scripts/code.bat index a05bea92d57..008c54fcbde 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -14,7 +14,7 @@ set NAMESHORT=%NAMESHORT:"=%.exe set CODE=".build\electron\%NAMESHORT%" :: Manage built-in extensions -if "%1"=="--builtin" goto builtin +if "%~1"=="--builtin" goto builtin :: Configuration set NODE_ENV=development diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index c211ba13701..3f66035c02e 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -49,7 +49,11 @@ require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); function pipeLoggingToParent() { const MAX_LENGTH = 100000; - // Prevent circular stringify and convert arguments to real array + /** + * Prevent circular stringify and convert arguments to real array + * + * @param {IArguments} args + */ function safeToArray(args) { const seen = []; const argsArray = []; diff --git a/src/bootstrap-node.js b/src/bootstrap-node.js index eaaf1dc6100..3956deab8d2 100644 --- a/src/bootstrap-node.js +++ b/src/bootstrap-node.js @@ -6,6 +6,42 @@ //@ts-check 'use strict'; +// Setup current working directory in all our node & electron processes +// - Windows: call `process.chdir()` to always set application folder as cwd +// - Posix: allow to change the current working dir via `VSCODE_CWD` if defined +// - all OS: store the `process.cwd()` inside `VSCODE_CWD` for consistent lookups +// // TODO@bpasero revisit if chdir() on Windows is needed in the future still +function setupCurrentWorkingDirectory() { + const path = require('path'); + + try { + let cwd = process.env['VSCODE_CWD']; + + // remember current working directory in environment + // unless it was given to us already from outside + if (typeof cwd !== 'string') { + cwd = process.cwd(); + process.env['VSCODE_CWD'] = cwd; + } + + // Windows: always set application folder as current working dir + if (process.platform === 'win32') { + process.chdir(path.dirname(process.execPath)); + } + + // Linux/macOS: allow to change current working dir based on env + else { + if (cwd !== process.cwd()) { + process.chdir(cwd); + } + } + } catch (err) { + console.error(err); + } +} + +setupCurrentWorkingDirectory(); + /** * Add support for redirecting the loading of node modules * diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 5ba762524ee..c6213db7c53 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -23,11 +23,10 @@ }(this, function () { const bootstrapLib = bootstrap(); const preloadGlobals = globals(); - const sandbox = preloadGlobals.context.sandbox; const webFrame = preloadGlobals.webFrame; const safeProcess = preloadGlobals.process; const configuration = parseWindowConfiguration(); - const useCustomProtocol = sandbox || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string'; + const useCustomProtocol = safeProcess.sandboxed || typeof safeProcess.env['ENABLE_VSCODE_BROWSER_CODE_LOADING'] === 'string'; // Start to resolve process.env before anything gets load // so that we can run loading and resolving in parallel @@ -83,7 +82,7 @@ } // replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only) - if (!sandbox) { + if (!safeProcess.sandboxed) { require.define('fs', [], function () { return require.__$__nodeRequire('original-fs'); }); } @@ -115,7 +114,7 @@ // - sandbox: we list paths of webpacked modules to help the loader // - non-sandbox: we signal that any module that does not begin with // `vs/` should be loaded using node.js require() - if (sandbox) { + if (safeProcess.sandboxed) { loaderConfig.paths = { 'vscode-textmate': `../node_modules/vscode-textmate/release/main`, 'vscode-oniguruma': `../node_modules/vscode-oniguruma/release/main`, diff --git a/src/bootstrap.js b/src/bootstrap.js index 3c8b0bc0238..02c2ad09906 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -180,7 +180,7 @@ } /** - * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').IPartialNodeProcess | NodeJS.Process} + * @returns {import('./vs/base/parts/sandbox/electron-sandbox/globals').ISandboxNodeProcess | NodeJS.Process} */ function safeProcess() { if (typeof process !== 'undefined') { diff --git a/src/main.js b/src/main.js index 7203aec3f51..2db5485da2d 100644 --- a/src/main.js +++ b/src/main.js @@ -15,7 +15,7 @@ const os = require('os'); const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); -const { getDefaultUserDataPath } = require('./vs/base/node/userDataPath'); +const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath'); /** @type {Partial} */ const product = require('../product.json'); const { app, protocol, crashReporter } = require('electron'); @@ -49,9 +49,6 @@ if (portable && portable.isPortable) { app.setAppLogsPath(path.join(userDataPath, 'logs')); } -// Update cwd based on environment and platform -setCurrentWorkingDirectory(); - // Register custom schemes with privileges protocol.registerSchemesAsPrivileged([ { @@ -161,7 +158,7 @@ function configureCommandlineSwitchesSync(cliArgs) { // Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775 'enable-proposed-api', - // TODO@bpasero remove me once testing is done on `vscode-file` protocol + // TODO@sandbox remove me once testing is done on `vscode-file` protocol // (all traces of `enable-browser-code-loading` and `ENABLE_VSCODE_BROWSER_CODE_LOADING`) 'enable-browser-code-loading' ]; @@ -424,19 +421,6 @@ function getJSFlags(cliArgs) { return jsFlags.length > 0 ? jsFlags.join(' ') : null; } -/** - * @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs - * - * @returns {string} - */ -function getUserDataPath(cliArgs) { - if (portable.isPortable) { - return path.join(portable.portableDataPath, 'user-data'); - } - - return path.resolve(cliArgs['user-data-dir'] || getDefaultUserDataPath()); -} - /** * @returns {import('./vs/platform/environment/common/argv').NativeParsedArgs} */ @@ -454,19 +438,6 @@ function parseCLIArgs() { }); } -function setCurrentWorkingDirectory() { - try { - if (process.platform === 'win32') { - process.env['VSCODE_CWD'] = process.cwd(); // remember as environment variable - process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd - } else if (process.env['VSCODE_CWD']) { - process.chdir(process.env['VSCODE_CWD']); - } - } catch (err) { - console.error(err); - } -} - function registerListeners() { /** @@ -516,7 +487,7 @@ function getNodeCachedDir() { return new class { constructor() { - this.value = this._compute(); + this.value = this.compute(); } async ensureExists() { @@ -531,7 +502,7 @@ function getNodeCachedDir() { } } - _compute() { + compute() { if (process.argv.indexOf('--no-cached-data') > 0) { return undefined; } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index 69e8593f671..9dff71689ed 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -3,22 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; - -/** - * @deprecated use `FileAccess.asFileUri(relativePath, requireFn).fsPath` - */ -export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string { - return getUriFromAmdModule(requirefn, relativePath).fsPath; -} - -/** - * @deprecated use `FileAccess.asFileUri()` for node.js contexts or `FileAccess.asBrowserUri` for browser contexts. - */ -export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI { - return URI.parse(requirefn.toUrl(relativePath)); -} - export abstract class LoaderStats { abstract get amdLoad(): [string, number][]; abstract get amdInvoke(): [string, number][]; @@ -26,10 +10,7 @@ export abstract class LoaderStats { abstract get nodeEval(): [string, number][]; abstract get nodeRequireTotal(): number; - static get(): LoaderStats { - - const amdLoadScript = new Map(); const amdInvokeFactory = new Map(); const nodeRequire = new Map(); diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index e61f64b9d42..78aecad6eba 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -27,37 +27,38 @@ export interface IProcessEnvironment { [key: string]: string; } +/** + * This interface is intentionally not identical to node.js + * process because it also works in sandboxed environments + * where the process object is implemented differently. We + * define the properties here that we need for `platform` + * to work and nothing else. + */ export interface INodeProcess { - platform: 'win32' | 'linux' | 'darwin'; + platform: string; env: IProcessEnvironment; - nextTick: Function; + nextTick?: (callback: (...args: any[]) => void) => void; versions?: { electron?: string; }; - sandboxed?: boolean; // Electron + sandboxed?: boolean; type?: string; - cwd(): string; + cwd: () => string; } + declare const process: INodeProcess; -declare const global: any; +declare const global: unknown; +declare const self: unknown; -interface INavigator { - userAgent: string; - language: string; - maxTouchPoints?: number; -} -declare const navigator: INavigator; -declare const self: any; - -const _globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {} as any); +export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); let nodeProcess: INodeProcess | undefined = undefined; if (typeof process !== 'undefined') { // Native environment (non-sandboxed) nodeProcess = process; -} else if (typeof _globals.vscode !== 'undefined') { +} else if (typeof globals.vscode !== 'undefined') { // Native environment (sandboxed) - nodeProcess = _globals.vscode.process; + nodeProcess = globals.vscode.process; } const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer'; @@ -83,6 +84,13 @@ export const browserCodeLoadingCacheStrategy: 'none' | 'code' | 'bypassHeatCheck })(); export const isPreferringBrowserCodeLoad = typeof browserCodeLoadingCacheStrategy === 'string'; +interface INavigator { + userAgent: string; + language: string; + maxTouchPoints?: number; +} +declare const navigator: INavigator; + // Web environment if (typeof navigator === 'object' && !isElectronRenderer) { _userAgent = navigator.userAgent; @@ -197,10 +205,8 @@ export const locale = _locale; */ export const translationsConfigFile = _translationsConfigFile; -export const globals: any = _globals; - interface ISetImmediate { - (callback: (...args: any[]) => void): void; + (callback: (...args: unknown[]) => void): void; } export const setImmediate: ISetImmediate = (function defineSetImmediate() { @@ -235,11 +241,11 @@ export const setImmediate: ISetImmediate = (function defineSetImmediate() { globals.postMessage({ vscodeSetImmediateId: myId }, '*'); }; } - if (nodeProcess && typeof nodeProcess.nextTick === 'function') { + if (typeof nodeProcess?.nextTick === 'function') { return nodeProcess.nextTick.bind(nodeProcess); } const _promise = Promise.resolve(); - return (callback: (...args: any[]) => void) => _promise.then(callback); + return (callback: (...args: unknown[]) => void) => _promise.then(callback); })(); export const enum OperatingSystem { diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 4b9dde87290..0cc42d3822f 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -5,26 +5,27 @@ import { isWindows, isMacintosh, setImmediate, globals, INodeProcess } from 'vs/base/common/platform'; -declare const process: INodeProcess; - -let safeProcess: INodeProcess; +let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) => void; }; // Native node.js environment +declare const process: INodeProcess; if (typeof process !== 'undefined') { - safeProcess = process; + safeProcess = { + get platform() { return process.platform; }, + get env() { return process.env; }, + cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }, + nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); } + }; } // Native sandbox environment else if (typeof globals.vscode !== 'undefined') { + const sandboxProcess: INodeProcess = globals.vscode.process; safeProcess = { - - // Supported - get platform(): 'win32' | 'linux' | 'darwin' { return globals.vscode.process.platform; }, - get env() { return globals.vscode.process.env; }, - nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }, - - // Unsupported - cwd(): string { return globals.vscode.process.env['VSCODE_CWD'] || globals.vscode.process.execPath.substr(0, globals.vscode.process.execPath.lastIndexOf(globals.vscode.process.platform === 'win32' ? '\\' : '/')); } + get platform() { return sandboxProcess.platform; }, + get env() { return sandboxProcess.env; }, + cwd() { return sandboxProcess.cwd(); }, + nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); } }; } @@ -33,16 +34,39 @@ else { safeProcess = { // Supported - get platform(): 'win32' | 'linux' | 'darwin' { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, + get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }, // Unsupported get env() { return Object.create(null); }, - cwd(): string { return '/'; } + cwd() { return '/'; } }; } +/** + * Provides safe access to the `cwd` property in node.js, sandboxed or web + * environments. + * + * Note: in web, this property is hardcoded to be `/`. + */ export const cwd = safeProcess.cwd; + +/** + * Provides safe access to the `env` property in node.js, sandboxed or web + * environments. + * + * Note: in web, this property is hardcoded to be `{}`. + */ export const env = safeProcess.env; + +/** + * Provides safe access to the `platform` property in node.js, sandboxed or web + * environments. + */ export const platform = safeProcess.platform; + +/** + * Provides safe access to the `nextTick` method in node.js, sandboxed or web + * environments. + */ export const nextTick = safeProcess.nextTick; diff --git a/src/vs/base/node/extpath.ts b/src/vs/base/node/extpath.ts index b43c407d519..eaf62cd8e12 100644 --- a/src/vs/base/node/extpath.ts +++ b/src/vs/base/node/extpath.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import { promisify } from 'util'; import { rtrim } from 'vs/base/common/strings'; import { sep, join, normalize, dirname, basename } from 'vs/base/common/path'; import { readdirSync } from 'vs/base/node/pfs'; @@ -52,7 +53,11 @@ export function realcaseSync(path: string): string | null { export async function realpath(path: string): Promise { try { - return await fs.promises.realpath(path); + // DO NOT USE `fs.promises.realpath` here as it internally + // calls `fs.native.realpath` which will result in subst + // drives to be resolved to their target on Windows + // https://github.com/microsoft/vscode/issues/118562 + return await promisify(fs.realpath)(path); } catch (error) { // We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization diff --git a/src/vs/base/node/powershell.ts b/src/vs/base/node/powershell.ts index c7cf92af959..77d74f796fa 100644 --- a/src/vs/base/node/powershell.ts +++ b/src/vs/base/node/powershell.ts @@ -6,7 +6,6 @@ import * as pfs from 'vs/base/node/pfs'; import * as os from 'os'; import * as path from 'vs/base/common/path'; -import { env } from 'vs/base/common/process'; // This is required, since parseInt("7-preview") will return 7. const IntRegex: RegExp = /^\d+$/; @@ -103,17 +102,17 @@ function getProgramFilesPath( if (!useAlternateBitness) { // Just use the native system bitness - return env.ProgramFiles || null; + return process.env.ProgramFiles || null; } // We might be a 64-bit process looking for 32-bit program files if (processArch === Arch.x64) { - return env['ProgramFiles(x86)'] || null; + return process.env['ProgramFiles(x86)'] || null; } // We might be a 32-bit process looking for 64-bit program files if (osArch === Arch.x64) { - return env.ProgramW6432 || null; + return process.env.ProgramW6432 || null; } // We're a 32-bit process on 32-bit Windows, there is no other Program Files dir @@ -194,12 +193,12 @@ async function findPSCoreWindowsInstallation( async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): Promise { // We can't proceed if there's no LOCALAPPDATA path - if (!env.LOCALAPPDATA) { + if (!process.env.LOCALAPPDATA) { return null; } // Find the base directory for MSIX application exe shortcuts - const msixAppDir = path.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps'); + const msixAppDir = path.join(process.env.LOCALAPPDATA, 'Microsoft', 'WindowsApps'); if (!await pfs.SymlinkSupport.existsDirectory(msixAppDir)) { return null; @@ -230,7 +229,7 @@ function findPSCoreDotnetGlobalTool(): IPossiblePowerShellExe { function findWinPS(): IPossiblePowerShellExe | null { const winPSPath = path.join( - env.windir!, + process.env.windir!, processArch === Arch.x86 && osArch !== Arch.x86 ? 'SysNative' : 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'); diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 72c1a613a28..0273af2365c 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import * as pfs from 'vs/base/node/pfs'; import * as cp from 'child_process'; import * as nls from 'vs/nls'; +import * as process from 'vs/base/common/process'; import * as Types from 'vs/base/common/types'; import { IStringDictionary } from 'vs/base/common/collections'; import * as Objects from 'vs/base/common/objects'; diff --git a/src/vs/base/node/shell.ts b/src/vs/base/node/shell.ts index 98861c0dd07..ae28f291d8f 100644 --- a/src/vs/base/node/shell.ts +++ b/src/vs/base/node/shell.ts @@ -45,7 +45,7 @@ function getSystemShellUnixLike(p: platform.Platform, env: platform.IProcessEnvi } if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) { - let unixLikeTerminal: string; + let unixLikeTerminal: string | undefined; if (platform.isWindows) { unixLikeTerminal = '/bin/bash'; // for WSL } else { diff --git a/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts b/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts index 98dde9e4eee..726b4689fc7 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import { TestServiceClient } from './testService'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; function createClient(): Client { return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), { diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 016c23a5d74..db7cdb89b0a 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -7,7 +7,7 @@ position: absolute; width: 600px; z-index: 2000; - padding-bottom: 6px; + padding: 0 1px 6px 1px; left: 50%; margin-left: -300px; } diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index 78c4aa678d6..b68c5aac16b 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -22,6 +22,8 @@ /** * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. + * + * @type {import('../electron-sandbox/electronTypes').IpcRenderer} */ ipcRenderer: { @@ -49,34 +51,46 @@ /** * @param {string} channel * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener + * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} */ on(channel, listener) { if (validateIPC(channel)) { ipcRenderer.on(channel, listener); + + return this; } }, /** * @param {string} channel * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener + * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} */ once(channel, listener) { if (validateIPC(channel)) { ipcRenderer.once(channel, listener); + + return this; } }, /** * @param {string} channel * @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener + * @returns {import('../electron-sandbox/electronTypes').IpcRenderer} */ removeListener(channel, listener) { if (validateIPC(channel)) { ipcRenderer.removeListener(channel, listener); + + return this; } } }, + /** + * @type {import('../electron-sandbox/globals').IpcMessagePort} + */ ipcMessagePort: { /** @@ -106,6 +120,8 @@ /** * Support for subset of methods of Electron's `webFrame` type. + * + * @type {import('../electron-sandbox/electronTypes').WebFrame} */ webFrame: { @@ -121,6 +137,8 @@ /** * Support for subset of methods of Electron's `crashReporter` type. + * + * @type {import('../electron-sandbox/electronTypes').CrashReporter} */ crashReporter: { @@ -138,6 +156,8 @@ * * Note: when `sandbox` is enabled, the only properties available * are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox + * + * @type {import('../electron-sandbox/globals').ISandboxNodeProcess} */ process: { get platform() { return process.platform; }, @@ -146,6 +166,14 @@ get versions() { return process.versions; }, get type() { return 'renderer'; }, get execPath() { return process.execPath; }, + get sandboxed() { return process.sandboxed; }, + + /** + * @returns {string} + */ + cwd() { + return process.env['VSCODE_CWD'] || process.execPath.substr(0, process.execPath.lastIndexOf(process.platform === 'win32' ? '\\' : '/')); + }, /** * @returns {Promise} @@ -171,20 +199,17 @@ /** * @param {string} type - * @param {() => void} callback + * @param {Function} callback + * @returns {import('../electron-sandbox/globals').ISandboxNodeProcess} */ on(type, callback) { if (validateProcessEventType(type)) { + // @ts-ignore process.on(type, callback); + + return this; } } - }, - - /** - * Some information about the context we are running in. - */ - context: { - get sandbox() { return process.sandboxed; } } }; diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index c45e4c20ded..7fc10080a4d 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -3,19 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals, IProcessEnvironment } from 'vs/base/common/platform'; +import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; import { ProcessMemoryInfo, CrashReporter, IpcRenderer, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; /** * In sandboxed renderers we cannot expose all of the `process` global of node.js */ -export interface IPartialNodeProcess { +export interface ISandboxNodeProcess extends INodeProcess { /** * The process.platform property returns a string identifying the operating system platform * on which the Node.js process is running. */ - readonly platform: 'win32' | 'linux' | 'darwin'; + readonly platform: string; /** * The process.arch property returns a string identifying the CPU architecture @@ -24,9 +24,14 @@ export interface IPartialNodeProcess { readonly arch: string; /** - * The type will always be Electron renderer. + * The type will always be `renderer`. */ - readonly type: 'renderer'; + readonly type: string; + + /** + * Whether the process is sandboxed or not. + */ + readonly sandboxed: boolean; /** * A list of versions for the current node.js/electron configuration. @@ -48,6 +53,11 @@ export interface IPartialNodeProcess { */ on: (type: string, callback: Function) => void; + /** + * The current working directory of the process. + */ + cwd: () => string; + /** * Resolves with a ProcessMemoryInfo * @@ -62,9 +72,6 @@ export interface IPartialNodeProcess { * process on macOS. */ getProcessMemoryInfo: () => Promise; -} - -export interface ISandboxNodeProcess extends IPartialNodeProcess { /** * A custom method we add to `process`: Resolve the true process environment to use and @@ -92,14 +99,6 @@ export interface ISandboxNodeProcess extends IPartialNodeProcess { getShellEnv(): Promise; } -export interface ISandboxContext { - - /** - * Whether the renderer runs with `sandbox` enabled or not. - */ - sandbox: boolean; -} - export interface IpcMessagePort { /** @@ -120,4 +119,3 @@ export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort; export const webFrame: WebFrame = globals.vscode.webFrame; export const crashReporter: CrashReporter = globals.vscode.crashReporter; export const process: ISandboxNodeProcess = globals.vscode.process; -export const context: ISandboxContext = globals.vscode.context; diff --git a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts index 688422c5242..45463115eee 100644 --- a/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts +++ b/src/vs/base/parts/sandbox/test/electron-sandbox/globals.test.ts @@ -8,7 +8,7 @@ import { ipcRenderer, crashReporter, webFrame, process } from 'vs/base/parts/san suite('Sandbox', () => { test('globals', () => { - assert.ok(typeof ipcRenderer.invoke === 'function'); + assert.ok(typeof ipcRenderer.send === 'function'); assert.ok(typeof crashReporter.addExtraParameter === 'function'); assert.ok(typeof webFrame.setZoomLevel === 'function'); assert.ok(typeof process.platform === 'string'); diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index cb185077fc2..67c188de8f0 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -8,9 +8,9 @@ import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { promises } from 'fs'; import { rimraf, writeFile } from 'vs/base/node/pfs'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -suite('Crypto', () => { +flakySuite('Crypto', () => { let testDir: string; diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 904a111f826..0eceb128b9a 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -10,10 +10,9 @@ import { join, sep } from 'vs/base/common/path'; import { generateUuid } from 'vs/base/common/uuid'; import { copy, exists, move, readdir, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFile, writeFileSync } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { isWindows } from 'vs/base/common/platform'; flakySuite('PFS', function () { diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.test.ts index e9f922f3360..5930f128d71 100644 --- a/src/vs/base/test/node/processes/processes.test.ts +++ b/src/vs/base/test/node/processes/processes.test.ts @@ -8,7 +8,7 @@ import * as cp from 'child_process'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import * as processes from 'vs/base/node/processes'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; function fork(id: string): cp.ChildProcess { const opts: any = { diff --git a/src/vs/base/test/node/testUtils.ts b/src/vs/base/test/node/testUtils.ts index 802d9fd1409..c425bd85626 100644 --- a/src/vs/base/test/node/testUtils.ts +++ b/src/vs/base/test/node/testUtils.ts @@ -5,12 +5,17 @@ import type { Suite } from 'mocha'; import { join } from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { return join(tmpdir, ...segments, generateUuid()); } +export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string { + return URI.parse(requirefn.toUrl(relativePath)).fsPath; +} + export function flakySuite(title: string, fn: (this: Suite) => void): Suite { return suite(title, function () { diff --git a/src/vs/base/test/node/uri.test.perf.ts b/src/vs/base/test/node/uri.test.perf.ts index 89e8c4c6050..654311832b2 100644 --- a/src/vs/base/test/node/uri.test.perf.ts +++ b/src/vs/base/test/node/uri.test.perf.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { readFileSync } from 'fs'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; suite('URI - perf', function () { diff --git a/src/vs/base/test/node/userDataPath.test.ts b/src/vs/base/test/node/userDataPath.test.ts deleted file mode 100644 index 74d0ae4f26e..00000000000 --- a/src/vs/base/test/node/userDataPath.test.ts +++ /dev/null @@ -1,15 +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 * as assert from 'assert'; -import { getDefaultUserDataPath } from 'vs/base/node/userDataPath'; - -suite('User data path', () => { - - test('getDefaultUserDataPath', () => { - const path = getDefaultUserDataPath(); - assert.ok(path.length > 0); - }); -}); diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index 360f7dfd04b..4f151af16e9 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -9,9 +9,8 @@ import { tmpdir } from 'os'; import { promises } from 'fs'; import { extract } from 'vs/base/node/zip'; import { rimraf, exists } from 'vs/base/node/pfs'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { createCancelablePromise } from 'vs/base/common/async'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; suite('Zip', () => { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 41a73455b5b..00df213a37c 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; @@ -32,9 +32,14 @@ interface LanguagePackFile { export class LanguagePackCachedDataCleaner extends Disposable { + private readonly _DataMaxAge = this._productService.quality !== 'stable' + ? 1000 * 60 * 60 * 24 * 7 // roughly 1 week + : 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months + constructor( @INativeEnvironmentService private readonly _environmentService: INativeEnvironmentService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @IProductService private readonly _productService: IProductService ) { super(); // We have no Language pack support for dev version (run from source) @@ -48,9 +53,6 @@ export class LanguagePackCachedDataCleaner extends Disposable { let handle: any = setTimeout(async () => { handle = undefined; this._logService.info('Starting to clean up unused language packs.'); - const maxAge = product.nameLong.indexOf('Insiders') >= 0 - ? 1000 * 60 * 60 * 24 * 7 // roughly 1 week - : 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months try { const installed: IStringDictionary = Object.create(null); const metaData: LanguagePackFile = JSON.parse(await fs.promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8')); @@ -84,7 +86,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { const stat = await fs.promises.stat(candidate); if (stat.isDirectory()) { const diff = now - stat.mtime.getTime(); - if (diff > maxAge) { + if (diff > this._DataMaxAge) { this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry)); await pfs.rimraf(candidate); } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index 7c37511ca73..fdb9aa7bb40 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -8,18 +8,19 @@ import { basename, dirname, join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { readdir, rimraf } from 'vs/base/node/pfs'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export class NodeCachedDataCleaner { - private static readonly _DataMaxAge = product.nameLong.indexOf('Insiders') >= 0 + private readonly _DataMaxAge = this.productService.quality !== 'stable' ? 1000 * 60 * 60 * 24 * 7 // roughly 1 week : 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months private readonly _disposables = new DisposableStore(); constructor( - private readonly nodeCachedDataDir: string | undefined + private readonly nodeCachedDataDir: string | undefined, + @IProductService private readonly productService: IProductService ) { this._manageCachedDataSoon(); } @@ -61,7 +62,7 @@ export class NodeCachedDataCleaner { // * only when old enough if (stats.isDirectory()) { const diff = now - stats.mtime.getTime(); - if (diff > NodeCachedDataCleaner._DataMaxAge) { + if (diff > this._DataMaxAge) { return rimraf(path); } } diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 549f3ce76d7..93484491b4f 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -132,7 +132,7 @@ class SharedProcessMain extends Disposable { // Instantiate Contributions this._register(combinedDisposable( - new NodeCachedDataCleaner(this.configuration.nodeCachedDataDir), + instantiationService.createInstance(NodeCachedDataCleaner, this.configuration.nodeCachedDataDir), instantiationService.createInstance(LanguagePackCachedDataCleaner), instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath), instantiationService.createInstance(LogsDataCleaner), @@ -144,8 +144,12 @@ class SharedProcessMain extends Disposable { private async initServices(): Promise { const services = new ServiceCollection(); + // Product + const productService = { _serviceBrand: undefined, ...product }; + services.set(IProductService, productService); + // Environment - const environmentService = new NativeEnvironmentService(this.configuration.args); + const environmentService = new NativeEnvironmentService(this.configuration.args, productService); services.set(INativeEnvironmentService, environmentService); // Log @@ -183,9 +187,6 @@ class SharedProcessMain extends Disposable { await storageService.initialize(); this._register(toDisposable(() => storageService.flush())); - // Product - services.set(IProductService, { _serviceBrand: undefined, ...product }); - // Request services.set(IRequestService, new SyncDescriptor(RequestService)); @@ -213,19 +214,19 @@ class SharedProcessMain extends Disposable { let telemetryService: ITelemetryService; let telemetryAppender: ITelemetryAppender; - if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { + if (!extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) { telemetryAppender = new TelemetryLogAppender(loggerService, environmentService); // Application Insights - if (product.aiConfig && product.aiConfig.asimovKey && isBuilt) { - const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey); + if (productService.aiConfig && productService.aiConfig.asimovKey && isBuilt) { + const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey); this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data telemetryAppender = combinedAppender(appInsightsAppender, telemetryAppender); } telemetryService = new TelemetryService({ appender: telemetryAppender, - commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, this.configuration.machineId, product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, this.configuration.machineId, productService.msftInternalDomains, installSourcePath), sendErrorTelemetry: true, piiPaths: [appRoot, extensionsPath] }, configurationService); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 3695b817778..408c0df3ee7 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -33,7 +33,7 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { FileProtocolHandler } from 'vs/code/electron-main/protocol'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -106,7 +106,8 @@ export class CodeApplication extends Disposable { @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IProductService private readonly productService: IProductService ) { super(); @@ -419,7 +420,7 @@ export class CodeApplication extends Disposable { // This will help Windows to associate the running program with // any shortcut that is pinned to the taskbar and prevent showing // two icons in the taskbar for the same app. - const win32AppUserModelId = product.win32AppUserModelId; + const win32AppUserModelId = this.productService.win32AppUserModelId; if (isWindows && win32AppUserModelId) { app.setAppUserModelId(win32AppUserModelId); } @@ -588,10 +589,10 @@ export class CodeApplication extends Disposable { services.set(IURLService, new SyncDescriptor(NativeURLService)); // Telemetry - if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!product.enableTelemetry) { + if (!this.environmentMainService.isExtensionDevelopment && !this.environmentMainService.args['disable-telemetry'] && !!this.productService.enableTelemetry) { const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, product.commit, product.version, machineId, product.msftInternalDomains, this.environmentMainService.installSourcePath); + const commonProperties = resolveCommonProperties(this.fileService, release(), process.arch, this.productService.commit, this.productService.version, machineId, this.productService.msftInternalDomains, this.environmentMainService.installSourcePath); const piiPaths = [this.environmentMainService.appRoot, this.environmentMainService.extensionsPath]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; @@ -789,7 +790,7 @@ export class CodeApplication extends Disposable { urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); // Watch Electron URLs and forward them to the UrlService - this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService)); + this._register(new ElectronURLListener(pendingProtocolLinksToHandle, urlService, windowsMainService, this.environmentMainService, this.productService)); // Open our first window const args = this.environmentMainService.args; @@ -860,14 +861,14 @@ export class CodeApplication extends Disposable { private shouldBlockURI(uri: URI): boolean { if (uri.authority === Schemas.file && isWindows) { const res = dialog.showMessageBoxSync({ - title: product.nameLong, + title: this.productService.nameLong, type: 'question', buttons: [ mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")), mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")), ], cancelId: 1, - message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), product.nameShort), + message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri.fsPath, this.environmentMainService), this.productService.nameShort), detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"), noLink: true }); @@ -962,7 +963,7 @@ export class CodeApplication extends Disposable { })); // Windows: install mutex - const win32MutexName = product.win32MutexName; + const win32MutexName = this.productService.win32MutexName; if (isWindows && win32MutexName) { try { const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; @@ -1032,7 +1033,7 @@ export class CodeApplication extends Disposable { recordingStopped = true; // only once - const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath); + const path = await contentTracing.stopRecording(joinPath(this.environmentMainService.userHome, `${this.productService.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`).fsPath); if (!timeout) { dialogMainService.showMessageBox({ diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 70df692cf81..e56b69d889b 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -12,7 +12,7 @@ import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { IEncryptionMainService } from 'vs/platform/encryption/electron-main/encryptionMainService'; import { generateUuid } from 'vs/base/common/uuid'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationToken } from 'vs/base/common/cancellation'; interface ElectronAuthenticationResponseDetails extends AuthenticationResponseDetails { @@ -56,7 +56,7 @@ enum ProxyAuthState { export class ProxyAuthHandler extends Disposable { - private static PROXY_CREDENTIALS_SERVICE_KEY = `${product.urlProtocol}.proxy-credentials`; + private readonly PROXY_CREDENTIALS_SERVICE_KEY = `${this.productService.urlProtocol}.proxy-credentials`; private pendingProxyResolve: Promise | undefined = undefined; @@ -68,7 +68,8 @@ export class ProxyAuthHandler extends Disposable { @ILogService private readonly logService: ILogService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService + @IEncryptionMainService private readonly encryptionMainService: IEncryptionMainService, + @IProductService private readonly productService: IProductService ) { super(); @@ -153,7 +154,7 @@ export class ProxyAuthHandler extends Disposable { let storedUsername: string | undefined = undefined; let storedPassword: string | undefined = undefined; try { - const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + const encryptedSerializedProxyCredentials = await this.nativeHostMainService.getPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); if (encryptedSerializedProxyCredentials) { const credentials: Credentials = JSON.parse(await this.encryptionMainService.decrypt(encryptedSerializedProxyCredentials)); @@ -211,9 +212,9 @@ export class ProxyAuthHandler extends Disposable { try { if (reply.remember) { const encryptedSerializedCredentials = await this.encryptionMainService.encrypt(JSON.stringify(credentials)); - await this.nativeHostMainService.setPassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials); + await this.nativeHostMainService.setPassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash, encryptedSerializedCredentials); } else { - await this.nativeHostMainService.deletePassword(undefined, ProxyAuthHandler.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); + await this.nativeHostMainService.deletePassword(undefined, this.PROXY_CREDENTIALS_SERVICE_KEY, authInfoHash); } } catch (error) { this.logService.error(error); // handle gracefully diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 63494a0d650..19403af689d 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -53,6 +53,7 @@ import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/env import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { LoggerService } from 'vs/platform/log/node/loggerService'; +import { cwd } from 'vs/base/common/process'; /** * The main VS Code entry point. @@ -83,7 +84,7 @@ class CodeMain { const args = this.resolveArgs(); // Create services - const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService] = this.createServices(args); + const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService, productService] = this.createServices(args); try { @@ -93,7 +94,7 @@ class CodeMain { } catch (error) { // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentService, error); + this.handleStartupDataDirError(environmentService, productService.nameLong, error); throw error; } @@ -107,7 +108,7 @@ class CodeMain { // Create the main IPC server by trying to be the server // If this throws an error it means we are not the first // instance of VS Code running and so we would quit. - const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true); + const mainProcessNodeIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, productService, true); // Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906) bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel()); @@ -125,11 +126,15 @@ class CodeMain { } } - private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService] { + private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService, IProductService] { const services = new ServiceCollection(); + // Product + const productService = { _serviceBrand: undefined, ...product }; + services.set(IProductService, productService); + // Environment - const environmentMainService = new EnvironmentMainService(args); + const environmentMainService = new EnvironmentMainService(args, productService); const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment services.set(IEnvironmentMainService, environmentMainService); @@ -170,13 +175,10 @@ class CodeMain { // Signing services.set(ISignService, new SyncDescriptor(SignService)); - // Product - services.set(IProductService, { _serviceBrand: undefined, ...product }); - // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogService, productService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -217,7 +219,7 @@ class CodeMain { return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); } - private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -233,7 +235,7 @@ class CodeMain { if (error.code !== 'EADDRINUSE') { // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentMainService, error); + this.handleStartupDataDirError(environmentMainService, productService.nameLong, error); // Any other runtime error is just printed to the console throw error; @@ -249,8 +251,9 @@ class CodeMain { if (!retry || isWindows || error.code !== 'ECONNREFUSED') { if (error.code === 'EPERM') { this.showStartupWarningDialog( - localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), - localize('secondInstanceAdminDetail', "Please close the other instance and try again.") + localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", productService.nameShort), + localize('secondInstanceAdminDetail', "Please close the other instance and try again."), + productService.nameLong ); } @@ -268,7 +271,7 @@ class CodeMain { throw error; } - return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, false); + return this.doStartup(args, logService, environmentMainService, lifecycleMainService, instantiationService, productService, false); } // Tests from CLI require to be the only instance currently @@ -287,8 +290,9 @@ class CodeMain { if (!args.wait && !args.status) { startupWarningDialogHandle = setTimeout(() => { this.showStartupWarningDialog( - localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), - localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.") + localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort), + localize('secondInstanceNoResponseDetail', "Please close all other instances and try again."), + productService.nameLong ); }, 10000); } @@ -298,7 +302,7 @@ class CodeMain { // Process Info if (args.status) { return instantiationService.invokeFunction(async () => { - const diagnosticsService = new DiagnosticsService(NullTelemetryService); + const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService); const mainProcessInfo = await launchService.getMainProcessInfo(); const remoteDiagnostics = await launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics); @@ -342,23 +346,24 @@ class CodeMain { return mainProcessNodeIpcServer; } - private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, error: NodeJS.ErrnoException): void { + private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, title: string, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(folder, environmentMainService)); this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), - localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')) + localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')), + title ); } } - private showStartupWarningDialog(message: string, detail: string): void { + private showStartupWarningDialog(message: string, detail: string, title: string): void { // use sync variant here because we likely exit after this method // due to startup issues and otherwise the dialog seems to disappear // https://github.com/microsoft/vscode/issues/104493 dialog.showMessageBoxSync({ - title: product.nameLong, + title, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message, @@ -449,7 +454,7 @@ class CodeMain { } private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { - const cwd = process.env['VSCODE_CWD'] || process.cwd(); + const currentWorkingDir = cwd(); const result = args.map(arg => { let pathCandidate = String(arg); @@ -460,10 +465,10 @@ class CodeMain { } if (pathCandidate) { - pathCandidate = this.preparePath(cwd, pathCandidate); + pathCandidate = this.preparePath(currentWorkingDir, pathCandidate); } - const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd); + const sanitizedFilePath = sanitizeFilePath(pathCandidate, currentWorkingDir); const filePathBasename = basename(sanitizedFilePath); if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 7980eb79f66..d50ab90f149 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -46,6 +46,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { VSBuffer } from 'vs/base/common/buffer'; +import { cwd } from 'vs/base/common/process'; class CliMain extends Disposable { @@ -94,8 +95,12 @@ class CliMain extends Disposable { private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> { const services = new ServiceCollection(); + // Product + const productService = { _serviceBrand: undefined, ...product }; + services.set(IProductService, productService); + // Environment - const environmentService = new NativeEnvironmentService(this.argv); + const environmentService = new NativeEnvironmentService(this.argv, productService); services.set(INativeEnvironmentService, environmentService); // Init folders @@ -130,9 +135,6 @@ class CliMain extends Disposable { const stateService = new StateService(environmentService, logService); services.set(IStateService, stateService); - // Product - services.set(IProductService, { _serviceBrand: undefined, ...product }); - const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; // Request @@ -148,15 +150,15 @@ class CliMain extends Disposable { // Telemetry const appenders: AppInsightsAppender[] = []; - if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) { - if (product.aiConfig && product.aiConfig.asimovKey) { - appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey)); + if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && productService.enableTelemetry) { + if (productService.aiConfig && productService.aiConfig.asimovKey) { + appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey)); } const config: ITelemetryServiceConfig = { appender: combinedAppender(...appenders), sendErrorTelemetry: false, - commonProperties: resolveCommonProperties(fileService, release(), process.arch, product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(fileService, release(), process.arch, productService.commit, productService.version, stateService.getItem('telemetry.machineId'), productService.msftInternalDomains, installSourcePath), piiPaths: [appRoot, extensionsPath] }; @@ -216,7 +218,7 @@ class CliMain extends Disposable { } private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] { - return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input); + return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); } private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise { diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 35aa7a6dec2..d2fde020c4a 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -417,7 +417,7 @@ export class TextAreaHandler extends ViewPart { const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 1000 for a better experience - this._accessibilityPageSize = 2000; + this._accessibilityPageSize = 1000; } else { this._accessibilityPageSize = accessibilityPageSize; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index d912576a766..a60bb77be2a 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -699,8 +699,8 @@ export interface CodeAction { * @internal */ export const enum CodeActionTriggerType { - Auto = 1, - Manual = 2, + Invoke = 1, + Auto = 2, } /** diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 20dd744ca2f..4b26bc013c8 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -12,7 +12,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, Command, MultiCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -94,6 +94,9 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({ }] })) : undefined; +MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { submenu: MenuId.MenubarCopy, title: { value: nls.localize('copy as', "Copy As..."), original: 'Copy As...', }, group: '2_ccp', order: 3 }); +MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextCopy, title: { value: nls.localize('copy as', "Copy As..."), original: 'Copy As...', }, group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 }); + export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ id: 'editor.action.clipboardPasteAction', precondition: undefined, @@ -111,13 +114,13 @@ export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({ menuId: MenuId.MenubarEditMenu, group: '2_ccp', title: nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), - order: 3 + order: 4 }, { menuId: MenuId.EditorContext, group: CLIPBOARD_CONTEXT_MENU_GROUP, title: nls.localize('actions.clipboard.pasteLabel', "Paste"), when: EditorContextKeys.writable, - order: 3, + order: 4, }, { menuId: MenuId.CommandPalette, group: '', diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 7bbcacf2a2b..7a66be2ed6f 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -247,7 +247,7 @@ CommandsRegistry.registerCommand('_executeCodeActionProvider', async function (a const codeActionSet = await getCodeActions( model, validatedRangeOrSelection, - { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include } }, + { type: modes.CodeActionTriggerType.Invoke, filter: { includeSourceActions: true, include } }, Progress.None, CancellationToken.None); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 65c19a781f6..aa54079ff85 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -124,7 +124,7 @@ export class QuickFixController extends Disposable implements IEditorContributio MessageController.get(this._editor).closeMessage(); const triggerPosition = this._editor.getPosition(); - this._trigger({ type: CodeActionTriggerType.Manual, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); + this._trigger({ type: CodeActionTriggerType.Invoke, filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); } private _trigger(trigger: CodeActionTrigger) { diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index c3ef8c90f78..d6e380ddc78 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -247,7 +247,7 @@ export class CodeActionModel extends Disposable { } const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, Progress.None, token)); - if (trigger.trigger.type === CodeActionTriggerType.Manual) { + if (trigger.trigger.type === CodeActionTriggerType.Invoke) { this._progressService?.showWhile(actions, 250); } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index 9f037de5ae6..099789d9504 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -77,7 +77,7 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget.getValue().update(actions, newState.trigger, newState.position); - if (newState.trigger.type === CodeActionTriggerType.Manual) { + if (newState.trigger.type === CodeActionTriggerType.Invoke) { if (newState.trigger.filter?.include) { // Triggered for specific scope // Check to see if we want to auto apply. diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 53a80a51108..c4987968a98 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -127,7 +127,7 @@ suite('CodeAction', () => { new CodeActionItem(testData.tsLint.abc, provider) ]; - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Invoke }, Progress.None, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); diff --git a/src/vs/editor/contrib/hover/markerHoverParticipant.ts b/src/vs/editor/contrib/hover/markerHoverParticipant.ts index bb55bc810c2..d13cdc5f9bd 100644 --- a/src/vs/editor/contrib/hover/markerHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markerHoverParticipant.ts @@ -46,7 +46,7 @@ export class MarkerHover implements IHoverPart { } const markerCodeActionTrigger: CodeActionTrigger = { - type: CodeActionTriggerType.Manual, + type: CodeActionTriggerType.Invoke, filter: { include: CodeActionKind.QuickFix } }; diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 23f6cd656f0..2dac4fc071b 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -32,8 +32,11 @@ import { CursorState } from 'vs/editor/common/controller/cursorCommon'; function announceCursorChange(previousCursorState: CursorState[], cursorState: CursorState[]): void { const cursorDiff = cursorState.filter(cs => !previousCursorState.find(pcs => pcs.equals(cs))); - const cursorPositions = cursorDiff.map(cs => `${cs.viewState.position.lineNumber}:${cs.viewState.position.column}`).join(', '); - status(nls.localize('cursorAdded', "Cursor added {0}", cursorPositions)); + if (cursorDiff.length >= 1) { + const cursorPositions = cursorDiff.map(cs => `line ${cs.viewState.position.lineNumber} column ${cs.viewState.position.column}`).join(', '); + const msg = cursorDiff.length === 1 ? nls.localize('cursorAdded', "Cursor added: {0}", cursorPositions) : nls.localize('cursorsAdded', "Cursors added: {0}", cursorPositions); + status(msg); + } } export class InsertCursorAbove extends EditorAction { diff --git a/src/vs/editor/test/node/classification/typescript.test.ts b/src/vs/editor/test/node/classification/typescript.test.ts index 19c7b901e7f..48a1e637f7e 100644 --- a/src/vs/editor/test/node/classification/typescript.test.ts +++ b/src/vs/editor/test/node/classification/typescript.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { StandardTokenType } from 'vs/editor/common/modes'; import * as fs from 'fs'; -// import { getPathFromAmdModule } from 'vs/base/common/amd'; +// import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; // import { parse } from 'vs/editor/common/modes/tokenization/typescript'; import { toStandardTokenType } from 'vs/editor/common/modes/supports/tokenization'; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e2d0e3a2cb2..51fa6146e9c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -87,6 +87,7 @@ export class MenuId { static readonly DebugWatchContext = new MenuId('DebugWatchContext'); static readonly DebugToolBar = new MenuId('DebugToolBar'); static readonly EditorContext = new MenuId('EditorContext'); + static readonly EditorContextCopy = new MenuId('EditorContextCopy'); static readonly EditorContextPeek = new MenuId('EditorContextPeek'); static readonly EditorTitle = new MenuId('EditorTitle'); static readonly EditorTitleRun = new MenuId('EditorTitleRun'); @@ -98,6 +99,7 @@ export class MenuId { static readonly MenubarAppearanceMenu = new MenuId('MenubarAppearanceMenu'); static readonly MenubarDebugMenu = new MenuId('MenubarDebugMenu'); static readonly MenubarEditMenu = new MenuId('MenubarEditMenu'); + static readonly MenubarCopy = new MenuId('MenubarCopy'); static readonly MenubarFileMenu = new MenuId('MenubarFileMenu'); static readonly MenubarGoMenu = new MenuId('MenubarGoMenu'); static readonly MenubarHelpMenu = new MenuId('MenubarHelpMenu'); diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index f831f9d49ba..561f0069f5a 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -23,6 +23,7 @@ import { createHash } from 'crypto'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; +import product from 'vs/platform/product/common/product'; flakySuite('BackupMainService', () => { @@ -104,7 +105,7 @@ flakySuite('BackupMainService', () => { backupWorkspacesPath = path.join(backupHome, 'workspaces.json'); existingTestFolder1 = URI.file(path.join(testDir, 'folder1')); - environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS)); + environmentService = new EnvironmentMainService(parseArgs(process.argv, OPTIONS), { _serviceBrand: undefined, ...product }); await fs.promises.mkdir(backupHome, { recursive: true }); diff --git a/src/vs/platform/checksum/test/node/checksumService.test.ts b/src/vs/platform/checksum/test/node/checksumService.test.ts index 4a27c170506..065804c4e16 100644 --- a/src/vs/platform/checksum/test/node/checksumService.test.ts +++ b/src/vs/platform/checksum/test/node/checksumService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { ChecksumService } from 'vs/platform/checksum/node/checksumService'; diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 953ec471b92..a66ffda0236 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -9,7 +9,7 @@ import { exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; @@ -213,7 +213,10 @@ export class DiagnosticsService implements IDiagnosticsService { declare readonly _serviceBrand: undefined; - constructor(@ITelemetryService private readonly telemetryService: ITelemetryService) { } + constructor( + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IProductService private readonly productService: IProductService + ) { } private formatMachineInfo(info: IMachineInfo): string { const output: string[] = []; @@ -227,7 +230,7 @@ export class DiagnosticsService implements IDiagnosticsService { private formatEnvironment(info: IMainProcessInfo): string { const output: string[] = []; - output.push(`Version: ${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); + output.push(`Version: ${this.productService.nameShort} ${this.productService.version} (${this.productService.commit || 'Commit unknown'}, ${this.productService.date || 'Date unknown'})`); output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`); const cpus = osLib.cpus(); if (cpus && cpus.length > 0) { @@ -481,7 +484,7 @@ export class DiagnosticsService implements IDiagnosticsService { // Format name with indent let name: string; if (isRoot) { - name = item.pid === mainPid ? `${product.applicationName} main` : 'remote agent'; + name = item.pid === mainPid ? `${this.productService.applicationName} main` : 'remote agent'; } else { name = `${' '.repeat(indent)} ${item.name}`; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index b4558e7e146..a10b16fdb53 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -108,7 +108,7 @@ export interface INativeEnvironmentService extends IEnvironmentService { // --- CLI Arguments args: NativeParsedArgs; - // --- paths + // --- data paths appRoot: string; userHome: URI; appSettingsHome: URI; @@ -117,12 +117,12 @@ export interface INativeEnvironmentService extends IEnvironmentService { machineSettingsResource: URI; installSourcePath: string; - // --- Extensions + // --- extensions extensionsPath: string; extensionsDownloadPath: string; builtinExtensionsPath: string; - // --- Smoke test support + // --- smoke test support driverHandle?: string; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts new file mode 100644 index 00000000000..9fc2f174a9c --- /dev/null +++ b/src/vs/platform/environment/common/environmentService.ts @@ -0,0 +1,257 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IProductService } from 'vs/platform/product/common/productService'; +import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { dirname, join, normalize, resolve } from 'vs/base/common/path'; +import { joinPath } from 'vs/base/common/resources'; +import { memoize } from 'vs/base/common/decorators'; +import { toLocalISOString } from 'vs/base/common/date'; +import { FileAccess } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; +import { env } from 'vs/base/common/process'; + +export interface INativeEnvironmentPaths { + + /** + * The user data directory to use for anything that should be + * persisted except for the content that is meant for the `homeDir`. + * + * Only one instance of VSCode can use the same `userDataDir`. + */ + userDataDir: string + + /** + * The user home directory mainly used for persisting extensions + * and global configuration that should be shared across all + * versions. + */ + homeDir: string; + + /** + * OS tmp dir. + */ + tmpDir: string, +} + +export abstract class AbstractNativeEnvironmentService implements INativeEnvironmentService { + + declare readonly _serviceBrand: undefined; + + @memoize + get appRoot(): string { return dirname(FileAccess.asFileUri('', require).fsPath); } + + @memoize + get userHome(): URI { return URI.file(this.paths.homeDir); } + + @memoize + get userDataPath(): string { return this.paths.userDataDir; } + + @memoize + get appSettingsHome(): URI { return URI.file(join(this.userDataPath, 'User')); } + + @memoize + get tmpDir(): URI { return URI.file(this.paths.tmpDir); } + + @memoize + get userRoamingDataHome(): URI { return this.appSettingsHome; } + + @memoize + get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } + + @memoize + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } + + get logsPath(): string { + if (!this.args.logsPath) { + const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); + this.args.logsPath = join(this.userDataPath, 'logs', key); + } + + return this.args.logsPath; + } + + @memoize + get userDataSyncLogResource(): URI { return URI.file(join(this.logsPath, 'userDataSync.log')); } + + @memoize + get sync(): 'on' | 'off' | undefined { return this.args.sync; } + + @memoize + get machineSettingsResource(): URI { return joinPath(URI.file(join(this.userDataPath, 'Machine')), 'settings.json'); } + + @memoize + get globalStorageHome(): URI { return URI.joinPath(this.appSettingsHome, 'globalStorage'); } + + @memoize + get workspaceStorageHome(): URI { return URI.joinPath(this.appSettingsHome, 'workspaceStorage'); } + + @memoize + get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } + + @memoize + get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } + + @memoize + get argvResource(): URI { + const vscodePortable = env['VSCODE_PORTABLE']; + if (vscodePortable) { + return URI.file(join(vscodePortable, 'argv.json')); + } + + return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json'); + } + + @memoize + get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } + + @memoize + get isExtensionDevelopment(): boolean { return !!this.args.extensionDevelopmentPath; } + + @memoize + get untitledWorkspacesHome(): URI { return URI.file(join(this.userDataPath, 'Workspaces')); } + + @memoize + get installSourcePath(): string { return join(this.userDataPath, 'installSource'); } + + @memoize + get builtinExtensionsPath(): string { + const cliBuiltinExtensionsDir = this.args['builtin-extensions-dir']; + if (cliBuiltinExtensionsDir) { + return resolve(cliBuiltinExtensionsDir); + } + + return normalize(join(FileAccess.asFileUri('', require).fsPath, '..', 'extensions')); + } + + get extensionsDownloadPath(): string { + const cliExtensionsDownloadDir = this.args['extensions-download-dir']; + if (cliExtensionsDownloadDir) { + return resolve(cliExtensionsDownloadDir); + } + + return join(this.userDataPath, 'CachedExtensionVSIXs'); + } + + @memoize + get extensionsPath(): string { + const cliExtensionsDir = this.args['extensions-dir']; + if (cliExtensionsDir) { + return resolve(cliExtensionsDir); + } + + const vscodeExtensions = env['VSCODE_EXTENSIONS']; + if (vscodeExtensions) { + return vscodeExtensions; + } + + const vscodePortable = env['VSCODE_PORTABLE']; + if (vscodePortable) { + return join(vscodePortable, 'extensions'); + } + + return joinPath(this.userHome, this.productService.dataFolderName, 'extensions').fsPath; + } + + @memoize + get extensionDevelopmentLocationURI(): URI[] | undefined { + const extensionDevelopmentPaths = this.args.extensionDevelopmentPath; + if (Array.isArray(extensionDevelopmentPaths)) { + return extensionDevelopmentPaths.map(extensionDevelopmentPath => { + if (/^[^:/?#]+?:\/\//.test(extensionDevelopmentPath)) { + return URI.parse(extensionDevelopmentPath); + } + + return URI.file(normalize(extensionDevelopmentPath)); + }); + } + + return undefined; + } + + @memoize + get extensionDevelopmentKind(): ExtensionKind[] | undefined { + return this.args.extensionDevelopmentKind?.map(kind => kind === 'ui' || kind === 'workspace' || kind === 'web' ? kind : 'workspace'); + } + + @memoize + get extensionTestsLocationURI(): URI | undefined { + const extensionTestsPath = this.args.extensionTestsPath; + if (extensionTestsPath) { + if (/^[^:/?#]+?:\/\//.test(extensionTestsPath)) { + return URI.parse(extensionTestsPath); + } + + return URI.file(normalize(extensionTestsPath)); + } + + return undefined; + } + + get disableExtensions(): boolean | string[] { + if (this.args['disable-extensions']) { + return true; + } + + const disableExtensions = this.args['disable-extension']; + if (disableExtensions) { + if (typeof disableExtensions === 'string') { + return [disableExtensions]; + } + + if (Array.isArray(disableExtensions) && disableExtensions.length > 0) { + return disableExtensions; + } + } + + return false; + } + + @memoize + get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this.args, this.isBuilt); } + get debugRenderer(): boolean { return !!this.args.debugRenderer; } + + get isBuilt(): boolean { return !env['VSCODE_DEV']; } + get verbose(): boolean { return !!this.args.verbose; } + get logLevel(): string | undefined { return this.args.log; } + + @memoize + get serviceMachineIdResource(): URI { return joinPath(URI.file(this.userDataPath), 'machineid'); } + + get crashReporterId(): string | undefined { return this.args['crash-reporter-id']; } + get crashReporterDirectory(): string | undefined { return this.args['crash-reporter-directory']; } + + get driverHandle(): string | undefined { return this.args['driver']; } + + @memoize + get telemetryLogResource(): URI { return URI.file(join(this.logsPath, 'telemetry.log')); } + get disableTelemetry(): boolean { return !!this.args['disable-telemetry']; } + + get args(): NativeParsedArgs { return this._args; } + + constructor( + private readonly _args: NativeParsedArgs, + private readonly paths: INativeEnvironmentPaths, + protected readonly productService: IProductService + ) { } +} + +export function parseExtensionHostPort(args: NativeParsedArgs, isBuild: boolean): IExtensionHostDebugParams { + return parseDebugPort(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId); +} + +export function parseSearchPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { + return parseDebugPort(args['inspect-search'], args['inspect-brk-search'], 5876, isBuild); +} + +function parseDebugPort(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string): IExtensionHostDebugParams { + const portStr = debugBrkArg || debugArg; + const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); + const brk = port ? Boolean(!!debugBrkArg) : false; + + return { port, break: brk, debugId }; +} diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts index 916e4e00a45..912cf8e9594 100644 --- a/src/vs/platform/environment/electron-main/environmentMainService.ts +++ b/src/vs/platform/environment/electron-main/environmentMainService.ts @@ -9,7 +9,6 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { createStaticIPCHandle } from 'vs/base/parts/ipc/node/ipc.net'; -import product from 'vs/platform/product/common/product'; export const IEnvironmentMainService = refineServiceDecorator(IEnvironmentService); @@ -51,19 +50,19 @@ export class EnvironmentMainService extends NativeEnvironmentService implements get backupWorkspacesPath(): string { return join(this.backupHome, 'workspaces.json'); } @memoize - get mainIPCHandle(): string { return createStaticIPCHandle(this.userDataPath, 'main', product.version); } + get mainIPCHandle(): string { return createStaticIPCHandle(this.userDataPath, 'main', this.productService.version); } @memoize - get sandbox(): boolean { return !!this._args['__sandbox']; } + get sandbox(): boolean { return !!this.args['__sandbox']; } @memoize - get driverVerbose(): boolean { return !!this._args['driver-verbose']; } + get driverVerbose(): boolean { return !!this.args['driver-verbose']; } @memoize - get disableUpdates(): boolean { return !!this._args['disable-updates']; } + get disableUpdates(): boolean { return !!this.args['disable-updates']; } @memoize - get disableKeytar(): boolean { return !!this._args['disable-keytar']; } + 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 8a5b798e3b7..8b655f51bea 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -262,7 +262,7 @@ export function formatOptions(options: OptionDescriptions, columns: number) } function indent(count: number): string { - return (' ').repeat(count); + return ' '.repeat(count); } function wrapText(text: string, columns: number): string[] { @@ -319,4 +319,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve export function buildVersionMessage(version: string | undefined, commit: string | undefined): string { return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } - diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 8161e3d0c35..23ea7c1c84e 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -4,248 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { homedir, tmpdir } from 'os'; -import product from 'vs/platform/product/common/product'; -import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { getDefaultUserDataPath } from 'vs/base/node/userDataPath'; -import { dirname, join, normalize, resolve } from 'vs/base/common/path'; -import { joinPath } from 'vs/base/common/resources'; -import { memoize } from 'vs/base/common/decorators'; -import { toLocalISOString } from 'vs/base/common/date'; -import { FileAccess } from 'vs/base/common/network'; -import { URI } from 'vs/base/common/uri'; -import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; +import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; +import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; +import { IProductService } from 'vs/platform/product/common/productService'; -export class NativeEnvironmentService implements INativeEnvironmentService { +export class NativeEnvironmentService extends AbstractNativeEnvironmentService { - declare readonly _serviceBrand: undefined; - - get args(): NativeParsedArgs { return this._args; } - - @memoize - get appRoot(): string { return dirname(FileAccess.asFileUri('', require).fsPath); } - - readonly logsPath: string; - - @memoize - get userHome(): URI { return URI.file(homedir()); } - - @memoize - get userDataPath(): string { - const vscodePortable = process.env['VSCODE_PORTABLE']; - if (vscodePortable) { - return join(vscodePortable, 'user-data'); - } - - return parseUserDataDir(this._args, process); - } - - @memoize - get appSettingsHome(): URI { return URI.file(join(this.userDataPath, 'User')); } - - @memoize - get tmpDir(): URI { return URI.file(tmpdir()); } - - @memoize - get userRoamingDataHome(): URI { return this.appSettingsHome; } - - @memoize - get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } - - @memoize - get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'sync'); } - - @memoize - get userDataSyncLogResource(): URI { return URI.file(join(this.logsPath, 'userDataSync.log')); } - - @memoize - get sync(): 'on' | 'off' | undefined { return this.args.sync; } - - @memoize - get machineSettingsResource(): URI { return joinPath(URI.file(join(this.userDataPath, 'Machine')), 'settings.json'); } - - @memoize - get globalStorageHome(): URI { return URI.joinPath(this.appSettingsHome, 'globalStorage'); } - - @memoize - get workspaceStorageHome(): URI { return URI.joinPath(this.appSettingsHome, 'workspaceStorage'); } - - @memoize - get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } - - @memoize - get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } - - @memoize - get argvResource(): URI { - const vscodePortable = process.env['VSCODE_PORTABLE']; - if (vscodePortable) { - return URI.file(join(vscodePortable, 'argv.json')); - } - - return joinPath(this.userHome, product.dataFolderName, 'argv.json'); - } - - @memoize - get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } - - @memoize - get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } - - @memoize - get untitledWorkspacesHome(): URI { return URI.file(join(this.userDataPath, 'Workspaces')); } - - @memoize - get installSourcePath(): string { return join(this.userDataPath, 'installSource'); } - - @memoize - get builtinExtensionsPath(): string { - const fromArgs = parsePathArg(this._args['builtin-extensions-dir'], process); - if (fromArgs) { - return fromArgs; - } else { - return normalize(join(FileAccess.asFileUri('', require).fsPath, '..', 'extensions')); - } - } - - get extensionsDownloadPath(): string { - const fromArgs = parsePathArg(this._args['extensions-download-dir'], process); - if (fromArgs) { - return fromArgs; - } else { - return join(this.userDataPath, 'CachedExtensionVSIXs'); - } - } - - @memoize - get extensionsPath(): string { - const fromArgs = parsePathArg(this._args['extensions-dir'], process); - - if (fromArgs) { - return fromArgs; - } - - const vscodeExtensions = process.env['VSCODE_EXTENSIONS']; - if (vscodeExtensions) { - return vscodeExtensions; - } - - const vscodePortable = process.env['VSCODE_PORTABLE']; - if (vscodePortable) { - return join(vscodePortable, 'extensions'); - } - - return joinPath(this.userHome, product.dataFolderName, 'extensions').fsPath; - } - - @memoize - get extensionDevelopmentLocationURI(): URI[] | undefined { - const s = this._args.extensionDevelopmentPath; - if (Array.isArray(s)) { - return s.map(p => { - if (/^[^:/?#]+?:\/\//.test(p)) { - return URI.parse(p); - } - return URI.file(normalize(p)); - }); - } - return undefined; - } - - @memoize - get extensionDevelopmentKind(): ExtensionKind[] | undefined { - return this._args.extensionDevelopmentKind?.map(k => k === 'ui' || k === 'workspace' || k === 'web' ? k : 'workspace'); - } - - @memoize - get extensionTestsLocationURI(): URI | undefined { - const s = this._args.extensionTestsPath; - if (s) { - if (/^[^:/?#]+?:\/\//.test(s)) { - return URI.parse(s); - } - return URI.file(normalize(s)); - } - return undefined; - } - - get disableExtensions(): boolean | string[] { - if (this._args['disable-extensions']) { - return true; - } - const disableExtensions = this._args['disable-extension']; - if (disableExtensions) { - if (typeof disableExtensions === 'string') { - return [disableExtensions]; - } - if (Array.isArray(disableExtensions) && disableExtensions.length > 0) { - return disableExtensions; - } - } - return false; - } - - @memoize - get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); } - get debugRenderer(): boolean { return !!this._args.debugRenderer; } - - get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } - get verbose(): boolean { return !!this._args.verbose; } - get logLevel(): string | undefined { return this._args.log; } - - @memoize - get serviceMachineIdResource(): URI { return joinPath(URI.file(this.userDataPath), 'machineid'); } - - get crashReporterId(): string | undefined { return this._args['crash-reporter-id']; } - get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; } - - get driverHandle(): string | undefined { return this._args['driver']; } - - @memoize - get telemetryLogResource(): URI { return URI.file(join(this.logsPath, 'telemetry.log')); } - get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; } - - constructor(protected _args: NativeParsedArgs) { - if (!_args.logsPath) { - const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); - _args.logsPath = join(this.userDataPath, 'logs', key); - } - this.logsPath = _args.logsPath; + constructor(args: NativeParsedArgs, productService: IProductService) { + super(args, { + homeDir: homedir(), + tmpDir: tmpdir(), + userDataDir: getUserDataPath(args) + }, productService); } } - -export function parseExtensionHostPort(args: NativeParsedArgs, isBuild: boolean): IExtensionHostDebugParams { - return parseDebugPort(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId); -} - -export function parseSearchPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { - return parseDebugPort(args['inspect-search'], args['inspect-brk-search'], 5876, isBuild); -} - -function parseDebugPort(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string): IExtensionHostDebugParams { - const portStr = debugBrkArg || debugArg; - const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); - const brk = port ? Boolean(!!debugBrkArg) : false; - - return { port, break: brk, debugId }; -} - -export function parsePathArg(arg: string | undefined, process: NodeJS.Process): string | undefined { - if (!arg) { - return undefined; - } - - // Determine if the arg is relative or absolute, if relative use the original CWD - // (VSCODE_CWD), not the potentially overridden one (process.cwd()). - const resolved = resolve(arg); - - if (normalize(arg) === resolved) { - return resolved; - } - - return resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); -} - -export function parseUserDataDir(args: NativeParsedArgs, process: NodeJS.Process): string { - return parsePathArg(args['user-data-dir'], process) || resolve(getDefaultUserDataPath()); -} diff --git a/src/vs/base/node/userDataPath.d.ts b/src/vs/platform/environment/node/userDataPath.d.ts similarity index 53% rename from src/vs/base/node/userDataPath.d.ts rename to src/vs/platform/environment/node/userDataPath.d.ts index bc09b834a7e..a3cd8b73b1b 100644 --- a/src/vs/base/node/userDataPath.d.ts +++ b/src/vs/platform/environment/node/userDataPath.d.ts @@ -3,7 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; + /** - * Returns the user data path to use. + * Returns the user data path to use with some rules: + * - respect portable mode + * - respect --user-data-dir CLI argument + * - respect VSCODE_APPDATA environment variable */ -export function getDefaultUserDataPath(): string; +export function getUserDataPath(args: NativeParsedArgs): string; diff --git a/src/vs/base/node/userDataPath.js b/src/vs/platform/environment/node/userDataPath.js similarity index 67% rename from src/vs/base/node/userDataPath.js rename to src/vs/platform/environment/node/userDataPath.js index 93384cb8f4e..450e42aef2d 100644 --- a/src/vs/base/node/userDataPath.js +++ b/src/vs/platform/environment/node/userDataPath.js @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/// +/// //@ts-check (function () { @@ -16,12 +16,38 @@ */ function factory(path, os, productName) { - function getDefaultUserDataPath() { + /** + * @param {import('../../environment/common/argv').NativeParsedArgs} cliArgs + * + * @returns {string} + */ + function getUserDataPath(cliArgs) { + return path.resolve(doGetUserDataPath(cliArgs)); + } - // Support global VSCODE_APPDATA environment variable + /** + * @param {import('../../environment/common/argv').NativeParsedArgs} cliArgs + * + * @returns {string} + */ + function doGetUserDataPath(cliArgs) { + + // 1. Support portable mode + const portablePath = process.env['VSCODE_PORTABLE']; + if (portablePath) { + return path.join(portablePath, 'user-data'); + } + + // 2. Support explicit --user-data-dir + const cliPath = cliArgs['user-data-dir']; + if (cliPath) { + return cliPath; + } + + // 3. Support global VSCODE_APPDATA environment variable let appDataPath = process.env['VSCODE_APPDATA']; - // Otherwise check per platform + // 4. Otherwise check per platform if (!appDataPath) { switch (process.platform) { case 'win32': @@ -31,6 +57,7 @@ if (typeof userProfile !== 'string') { throw new Error('Windows: Unexpected undefined %USERPROFILE% environment variable'); } + appDataPath = path.join(userProfile, 'AppData', 'Roaming'); } break; @@ -49,19 +76,19 @@ } return { - getDefaultUserDataPath + getUserDataPath }; } if (typeof define === 'function') { - define(['require', 'path', 'os', 'vs/base/common/network', 'vs/base/common/resources'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('os')} */ os, /** @type {typeof import('../common/network')} */ network, /** @type {typeof import("../common/resources")} */ resources) { + define(['require', 'path', 'os', 'vs/base/common/network', 'vs/base/common/resources'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('os')} */ os, /** @type {typeof import('../../../base/common/network')} */ network, /** @type {typeof import("../../../base/common/resources")} */ resources) { const rootPath = resources.dirname(network.FileAccess.asFileUri('', require)); const pkg = require.__$__nodeRequire(resources.joinPath(rootPath, 'package.json').fsPath); return factory(path, os, pkg.name); }); // amd } else if (typeof module === 'object' && typeof module.exports === 'object') { - const pkg = require('../../../../package.json'); + const pkg = require('../../../../../package.json'); const path = require('path'); const os = require('os'); diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index ca33c22600d..15595ebc960 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'vs/base/common/path'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { parseExtensionHostPort, parseUserDataDir } from 'vs/platform/environment/node/environmentService'; +import { parseExtensionHostPort } from 'vs/platform/environment/common/environmentService'; +import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import product from 'vs/platform/product/common/product'; suite('EnvironmentService', () => { @@ -44,15 +45,6 @@ suite('EnvironmentService', () => { assert.deepStrictEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); }); - test('userDataPath', () => { - const parse = (a: string[], b: { cwd: () => string, env: { [key: string]: string } }) => parseUserDataDir(parseArgs(a, OPTIONS), b); - - assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: {} }), path.resolve('/foo/dir'), - 'should use cwd when --user-data-dir is specified'); - assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: { 'VSCODE_CWD': '/bar' } }), path.resolve('/bar/dir'), - 'should use VSCODE_CWD as the cwd when --user-data-dir is specified'); - }); - // https://github.com/microsoft/vscode/issues/78440 test('careful with boolean file names', function () { let actual = parseArgs(['-r', 'arg.txt'], OPTIONS); @@ -63,4 +55,15 @@ suite('EnvironmentService', () => { assert(actual['reuse-window']); assert.deepStrictEqual(actual._, ['true.txt']); }); + + test('userDataDir', () => { + const service1 = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), { _serviceBrand: undefined, ...product }); + assert.ok(service1.userDataPath.length > 0); + + const args = parseArgs(process.argv, OPTIONS); + args['user-data-dir'] = '/userDataDir/folder'; + + const service2 = new NativeEnvironmentService(args, { _serviceBrand: undefined, ...product }); + assert.notStrictEqual(service1.userDataPath, service2.userDataPath); + }); }); diff --git a/src/vs/platform/environment/test/node/userDataPath.test.ts b/src/vs/platform/environment/test/node/userDataPath.test.ts new file mode 100644 index 00000000000..e0eb596af9f --- /dev/null +++ b/src/vs/platform/environment/test/node/userDataPath.test.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; +import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; + +suite('User data path', () => { + + test('getUserDataPath - default', () => { + const path = getUserDataPath(parseArgs(process.argv, OPTIONS)); + assert.ok(path.length > 0); + }); + + test('getUserDataPath - portable mode', () => { + const origPortable = process.env['VSCODE_PORTABLE']; + try { + const portableDir = 'portable-dir'; + process.env['VSCODE_PORTABLE'] = portableDir; + + const path = getUserDataPath(parseArgs(process.argv, OPTIONS)); + assert.ok(path.includes(portableDir)); + } finally { + if (typeof origPortable === 'string') { + process.env['VSCODE_PORTABLE'] = origPortable; + } else { + delete process.env['VSCODE_PORTABLE']; + } + } + }); + + test('getUserDataPath - --user-data-dir', () => { + const cliUserDataDir = 'cli-data-dir'; + const args = parseArgs(process.argv, OPTIONS); + args['user-data-dir'] = cliUserDataDir; + + const path = getUserDataPath(args); + assert.ok(path.includes(cliUserDataDir)); + }); + + test('getUserDataPath - VSCODE_APPDATA', () => { + const origAppData = process.env['VSCODE_APPDATA']; + try { + const appDataDir = 'appdata-dir'; + process.env['VSCODE_APPDATA'] = appDataDir; + + const path = getUserDataPath(parseArgs(process.argv, OPTIONS)); + assert.ok(path.includes(appDataDir)); + } finally { + if (typeof origAppData === 'string') { + process.env['VSCODE_APPDATA'] = origAppData; + } else { + delete process.env['VSCODE_APPDATA']; + } + } + }); +}); diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts index a9ec2f35a4b..51066d5bedd 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { basename, join, } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { env } from 'vs/base/common/process'; import { IFileService } from 'vs/platform/files/common/files'; import { isWindows } from 'vs/base/common/platform'; import { isNonEmptyArray } from 'vs/base/common/arrays'; @@ -294,11 +294,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService { const exePaths: string[] = []; if (isWindows) { if (extensionTip.windowsPath) { - exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', process.env['USERPROFILE']!) - .replace('%ProgramFiles(x86)%', process.env['ProgramFiles(x86)']!) - .replace('%ProgramFiles%', process.env['ProgramFiles']!) - .replace('%APPDATA%', process.env['APPDATA']!) - .replace('%WINDIR%', process.env['WINDIR']!)); + exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', env['USERPROFILE']!) + .replace('%ProgramFiles(x86)%', env['ProgramFiles(x86)']!) + .replace('%ProgramFiles%', env['ProgramFiles']!) + .replace('%APPDATA%', env['APPDATA']!) + .replace('%WINDIR%', env['WINDIR']!)); } } else { exePaths.push(join('/usr/local/bin', exeName)); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index a551917c8c9..48403521d5f 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -113,12 +113,23 @@ export interface IAuthenticationContribution { readonly label: string; } -export interface IGettingStartedContent { +export interface IWalkthroughTask { 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 button: + | { title: string, link: string, command?: never } + | { title: string, command: string, link?: never }, + readonly media: { path: string, altText: string }, + readonly doneOn?: { command: string }; + readonly when?: string; +} + +export interface IWalkthrough { + readonly id: string, + readonly title: string; + readonly description: string; + readonly tasks: IWalkthroughTask[]; readonly when?: string; } @@ -141,12 +152,12 @@ export interface IExtensionContributions { readonly customEditors?: readonly IWebviewEditor[]; readonly codeActions?: readonly ICodeActionContribution[]; authentication?: IAuthenticationContribution[]; - gettingStarted?: IGettingStartedContent[]; + walkthroughs?: IWalkthrough[]; } export type ExtensionKind = 'ui' | 'workspace' | 'web'; - export type ExtensionWorkspaceTrustRequirement = false | 'onStart' | 'onDemand'; +export type ExtensionWorkspaceTrust = { required: ExtensionWorkspaceTrustRequirement, description?: string }; export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { return thing @@ -203,7 +214,7 @@ export interface IExtensionManifest { readonly enableProposedApi?: boolean; readonly api?: string; readonly scripts?: { [key: string]: string; }; - readonly requiresWorkspaceTrust?: ExtensionWorkspaceTrustRequirement; + readonly workspaceTrust?: ExtensionWorkspaceTrust; } export const enum ExtensionType { diff --git a/src/vs/platform/files/common/ipcFileSystemProvider.ts b/src/vs/platform/files/common/ipcFileSystemProvider.ts new file mode 100644 index 00000000000..6e938b0797b --- /dev/null +++ b/src/vs/platform/files/common/ipcFileSystemProvider.ts @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * 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, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { canceled } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; + +interface IFileChangeDto { + resource: UriComponents; + type: FileChangeType; +} + +/** + * An abstract file system provider that delegates all calls to a provided + * `IChannel` via IPC communication. + */ +export abstract class IPCFileSystemProvider extends Disposable implements + IFileSystemProviderWithFileReadWriteCapability, + IFileSystemProviderWithOpenReadWriteCloseCapability, + IFileSystemProviderWithFileReadStreamCapability, + IFileSystemProviderWithFileFolderCopyCapability { + + private readonly session: string = generateUuid(); + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChangeFile = this._onDidChange.event; + + private _onDidWatchErrorOccur = this._register(new Emitter()); + readonly onDidErrorOccur = this._onDidWatchErrorOccur.event; + + private readonly _onDidChangeCapabilities = this._register(new Emitter()); + readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; + + private _capabilities = FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.FileOpenReadWriteClose + | FileSystemProviderCapabilities.FileReadStream + | FileSystemProviderCapabilities.FileFolderCopy + | FileSystemProviderCapabilities.FileWriteUnlock; + get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } + + constructor(private readonly channel: IChannel) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.channel.listen('filechange', [this.session])(eventsOrError => { + if (Array.isArray(eventsOrError)) { + const events = eventsOrError; + this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); + } else { + const error = eventsOrError; + this._onDidWatchErrorOccur.fire(error); + } + })); + } + + protected setCaseSensitive(isCaseSensitive: boolean) { + if (isCaseSensitive) { + this._capabilities |= FileSystemProviderCapabilities.PathCaseSensitive; + } else { + this._capabilities &= ~FileSystemProviderCapabilities.PathCaseSensitive; + } + + this._onDidChangeCapabilities.fire(undefined); + } + + // --- forwarding calls + + stat(resource: URI): Promise { + return this.channel.call('stat', [resource]); + } + + open(resource: URI, opts: FileOpenOptions): Promise { + return this.channel.call('open', [resource, opts]); + } + + close(fd: number): Promise { + return this.channel.call('close', [fd]); + } + + async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const [bytes, bytesRead]: [VSBuffer, number] = await this.channel.call('read', [fd, pos, length]); + + // copy back the data that was written into the buffer on the remote + // side. we need to do this because buffers are not referenced by + // pointer, but only by value and as such cannot be directly written + // to from the other process. + data.set(bytes.buffer.slice(0, bytesRead), offset); + + return bytesRead; + } + + async readFile(resource: URI): Promise { + const buff = await this.channel.call('readFile', [resource]); + + return buff.buffer; + } + + readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { + const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); + + // Reading as file stream goes through an event to the remote side + const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { + + // data + if (dataOrErrorOrEnd instanceof VSBuffer) { + stream.write(dataOrErrorOrEnd.buffer); + } + + // end or error + else { + if (dataOrErrorOrEnd === 'end') { + stream.end(); + } else { + + // Since we receive data through a IPC channel, it is likely + // that the error was not serialized, or only partially. To + // ensure our API use is correct, we convert the data to an + // error here to forward it properly. + let error = dataOrErrorOrEnd; + if (!(error instanceof Error)) { + error = new Error(toErrorMessage(error)); + } + + stream.error(error); + stream.end(); + } + + // Signal to the remote side that we no longer listen + listener.dispose(); + } + }); + + // Support cancellation + token.onCancellationRequested(() => { + + // Ensure to end the stream properly with an error + // to indicate the cancellation. + stream.error(canceled()); + stream.end(); + + // Ensure to dispose the listener upon cancellation. This will + // bubble through the remote side as event and allows to stop + // reading the file. + listener.dispose(); + }); + + return stream; + } + + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]); + } + + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]); + } + + delete(resource: URI, opts: FileDeleteOptions): Promise { + return this.channel.call('delete', [resource, opts]); + } + + mkdir(resource: URI): Promise { + return this.channel.call('mkdir', [resource]); + } + + readdir(resource: URI): Promise<[string, FileType][]> { + return this.channel.call('readdir', [resource]); + } + + rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise { + return this.channel.call('rename', [resource, target, opts]); + } + + copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise { + return this.channel.call('copy', [resource, target, opts]); + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + const req = Math.random(); + this.channel.call('watch', [this.session, req, resource, opts]); + + return toDisposable(() => this.channel.call('unwatch', [this.session, req])); + } +} diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index 67ccfd9a6b1..a2aaa0faf79 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -78,7 +78,8 @@ suite('IndexedDB File Service', function () { disposables.add(userdataFileProvider); }; - setup(async () => { + setup(async function () { + this.timeout(15000); await reload(); }); diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index 6b17a097bf7..58574258ee2 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -8,9 +8,8 @@ import { tmpdir } from 'os'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { join, basename, dirname, posix } from 'vs/base/common/path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { copy, rimraf, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync, createReadStream, promises } from 'fs'; diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index c14c7b67a53..dedce29bccd 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -78,7 +78,7 @@ export interface ProcessExplorerStyles extends WindowStyles { export interface ProcessExplorerData extends WindowData { pid: number; styles: ProcessExplorerStyles; - platform: 'win32' | 'darwin' | 'linux'; + platform: string; applicationName: string; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index d75cbec53a1..c9ed978ec34 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import * as os from 'os'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { ICommonIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { BrowserWindow, ipcMain, screen, IpcMainEvent, Display } from 'electron'; @@ -43,7 +43,8 @@ export class IssueMainService implements ICommonIssueService { @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IDialogMainService private readonly dialogMainService: IDialogMainService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProductService private readonly productService: IProductService ) { this.registerListeners(); } @@ -410,12 +411,12 @@ export class IssueMainService implements ICommonIssueService { release: os.release(), }, product: { - nameShort: product.nameShort, - version: !!product.darwinUniversalAssetId ? `${product.version} (Universal)` : product.version, - commit: product.commit, - date: product.date, - reportIssueUrl: product.reportIssueUrl, - reportMarketplaceIssueUrl: product.reportMarketplaceIssueUrl + nameShort: this.productService.nameShort, + version: !!this.productService.darwinUniversalAssetId ? `${this.productService.version} (Universal)` : this.productService.version, + commit: this.productService.commit, + date: this.productService.date, + reportIssueUrl: this.productService.reportIssueUrl, + reportMarketplaceIssueUrl: this.productService.reportMarketplaceIssueUrl } }; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 89390d4e240..c5d69ba4f5f 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -16,6 +16,7 @@ import { Promises, Barrier, timeout } from 'vs/base/common/async'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { assertIsDefined } from 'vs/base/common/types'; +import { cwd } from 'vs/base/common/process'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -553,13 +554,13 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Windows: we are about to restart and as such we need to restore the original // current working directory we had on startup to get the exact same startup - // behaviour. As such, we briefly change back to the VSCODE_CWD and then when + // behaviour. As such, we briefly change back to that directory and then when // Code starts it will set it back to the installation directory again. try { if (isWindows) { - const vscodeCwd = process.env['VSCODE_CWD']; - if (vscodeCwd) { - process.chdir(vscodeCwd); + const currentWorkingDir = cwd(); + if (currentWorkingDir !== process.cwd()) { + process.chdir(currentWorkingDir); } } } catch (err) { diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index f0a2cf748a5..d4f42330e32 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -11,7 +11,7 @@ import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybinding import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { mnemonicMenuLabel } from 'vs/base/common/labels'; @@ -73,7 +73,8 @@ export class Menubar { @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProductService private readonly productService: IProductService ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -123,22 +124,22 @@ export class Menubar { this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); // Help Menu Items - const twitterUrl = product.twitterUrl; + const twitterUrl = this.productService.twitterUrl; if (twitterUrl) { this.fallbackMenuHandlers['workbench.action.openTwitterUrl'] = () => this.openUrl(twitterUrl, 'openTwitterUrl'); } - const requestFeatureUrl = product.requestFeatureUrl; + const requestFeatureUrl = this.productService.requestFeatureUrl; if (requestFeatureUrl) { this.fallbackMenuHandlers['workbench.action.openRequestFeatureUrl'] = () => this.openUrl(requestFeatureUrl, 'openUserVoiceUrl'); } - const reportIssueUrl = product.reportIssueUrl; + const reportIssueUrl = this.productService.reportIssueUrl; if (reportIssueUrl) { this.fallbackMenuHandlers['workbench.action.openIssueReporter'] = () => this.openUrl(reportIssueUrl, 'openReportIssues'); } - const licenseUrl = product.licenseUrl; + const licenseUrl = this.productService.licenseUrl; if (licenseUrl) { this.fallbackMenuHandlers['workbench.action.openLicenseUrl'] = () => { if (language) { @@ -150,7 +151,7 @@ export class Menubar { }; } - const privacyStatementUrl = product.privacyStatementUrl; + const privacyStatementUrl = this.productService.privacyStatementUrl; if (privacyStatementUrl && licenseUrl) { this.fallbackMenuHandlers['workbench.action.openPrivacyStatementUrl'] = () => { if (language) { @@ -268,7 +269,7 @@ export class Menubar { let macApplicationMenuItem: MenuItem; if (isMacintosh) { const applicationMenu = new Menu(); - macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu }); + macApplicationMenuItem = new MenuItem({ label: this.productService.nameShort, submenu: applicationMenu }); this.setMacApplicationMenu(applicationMenu); menubar.append(macApplicationMenuItem); } @@ -362,7 +363,7 @@ export class Menubar { } private setMacApplicationMenu(macApplicationMenu: Menu): void { - const about = this.createMenuItem(nls.localize('mAbout', "About {0}", product.nameLong), 'workbench.action.showAboutDialog'); + const about = this.createMenuItem(nls.localize('mAbout', "About {0}", this.productService.nameLong), 'workbench.action.showAboutDialog'); const checkForUpdates = this.getUpdateMenuItems(); let preferences; @@ -374,11 +375,11 @@ export class Menubar { const servicesMenu = new Menu(); const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu }); - const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' }); + const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", this.productService.nameLong), role: 'hide', accelerator: 'Command+H' }); const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideOthers', accelerator: 'Command+Alt+H' }); const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); const quit = new MenuItem(this.likeAction('workbench.action.quit', { - label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => { + label: nls.localize('miQuit', "Quit {0}", this.productService.nameLong), click: () => { const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); if ( this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 461e732f77a..eb071741717 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -24,7 +24,7 @@ import { arch, totalmem, release, platform, type, loadavg, freemem, cpus } from import { virtualMachineHint } from 'vs/base/node/id'; import { ILogService } from 'vs/platform/log/common/log'; import { dirname, join } from 'vs/base/common/path'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { memoize } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; import { ISharedProcess } from 'vs/platform/sharedProcess/node/sharedProcess'; @@ -49,7 +49,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IProductService private readonly productService: IProductService ) { super(); @@ -402,8 +403,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain sudoCommand.push('--file-write', `"${source.fsPath}"`, `"${target.fsPath}"`); const promptOptions = { - name: product.nameLong.replace('-', ''), - icns: (isMacintosh && this.environmentMainService.isBuilt) ? join(dirname(this.environmentMainService.appRoot), `${product.nameShort}.icns`) : undefined + name: this.productService.nameLong.replace('-', ''), + icns: (isMacintosh && this.environmentMainService.isBuilt) ? join(dirname(this.environmentMainService.appRoot), `${this.productService.nameShort}.icns`) : undefined }; sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error: string, stdout: string, stderr: string) => { @@ -430,7 +431,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain // Windows if (isWindows) { if (this.environmentMainService.isBuilt) { - return join(dirname(process.execPath), 'bin', `${product.applicationName}.cmd`); + return join(dirname(process.execPath), 'bin', `${this.productService.applicationName}.cmd`); } return join(this.environmentMainService.appRoot, 'scripts', 'code-cli.bat'); @@ -439,7 +440,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain // Linux if (isLinux) { if (this.environmentMainService.isBuilt) { - return join(dirname(process.execPath), 'bin', `${product.applicationName}`); + return join(dirname(process.execPath), 'bin', `${this.productService.applicationName}`); } return join(this.environmentMainService.appRoot, 'scripts', 'code-cli.sh'); diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index b7a6596e174..251ed36de91 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProductConfiguration } from 'vs/platform/product/common/productService'; +import { FileAccess } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { env } from 'vs/base/common/process'; -import { FileAccess } from 'vs/base/common/network'; import { dirname, joinPath } from 'vs/base/common/resources'; +import { IProductConfiguration } from 'vs/platform/product/common/productService'; let product: IProductConfiguration; @@ -32,7 +32,9 @@ if (isWeb || typeof require === 'undefined' || typeof require.__$__nodeRequire ! extensionAllowedProposedApi: [ 'ms-vscode.vscode-js-profile-flame', 'ms-vscode.vscode-js-profile-table', - 'ms-vscode.github-browser' + 'ms-vscode.github-browser', + 'ms-vscode.remotehub', + 'ms-vscode.remotehub-insiders' ], }); } 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 704b785db0e..897a2367af3 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -17,9 +17,13 @@ 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 } from 'vs/base/common/async'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; suite('StorageMainService', function () { + const productService: IProductService = { _serviceBrand: undefined, ...product }; + class TestStorageMainService extends StorageMainService { protected getStorageOptions(): IStorageMainOptions { @@ -122,14 +126,14 @@ suite('StorageMainService', function () { } test('basics (global)', function () { - const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService()); + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService), new StorageTestLifecycleMainService()); return testStorage(storageMainService.globalStorage, true); }); test('basics (workspace)', function () { const workspace = { id: generateUuid() }; - const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService()); + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService), new StorageTestLifecycleMainService()); return testStorage(storageMainService.workspaceStorage(workspace), false); }); @@ -137,7 +141,7 @@ suite('StorageMainService', function () { test('storage closed onWillShutdown', async function () { const lifecycleMainService = new StorageTestLifecycleMainService(); const workspace = { id: generateUuid() }; - const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), lifecycleMainService); + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService), lifecycleMainService); let workspaceStorage = storageMainService.workspaceStorage(workspace); let didCloseWorkspaceStorage = false; @@ -168,7 +172,7 @@ suite('StorageMainService', function () { }); test('storage closed before init works', async function () { - const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService()); + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService), new StorageTestLifecycleMainService()); const workspace = { id: generateUuid() }; let workspaceStorage = storageMainService.workspaceStorage(workspace); @@ -191,7 +195,7 @@ suite('StorageMainService', function () { }); test('storage closed before init awaits works', async function () { - const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService()); + const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService), new StorageTestLifecycleMainService()); const workspace = { id: generateUuid() }; let workspaceStorage = storageMainService.workspaceStorage(workspace); diff --git a/src/vs/platform/terminal/common/terminalRecorder.ts b/src/vs/platform/terminal/common/terminalRecorder.ts index 6341bcc17a5..e8a6e17c366 100644 --- a/src/vs/platform/terminal/common/terminalRecorder.ts +++ b/src/vs/platform/terminal/common/terminalRecorder.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess'; +import { IPtyHostProcessReplayEvent, ReplayEntry } from 'vs/platform/terminal/common/terminalProcess'; const MAX_RECORDER_DATA_SIZE = 1024 * 1024; // 1MB @@ -13,8 +13,6 @@ interface RecorderEntry { data: string[]; } -export interface ReplayEntry { cols: number; rows: number; data: string; } - export interface IRemoteTerminalProcessReplayEvent { events: ReplayEntry[]; } @@ -22,11 +20,10 @@ export interface IRemoteTerminalProcessReplayEvent { export class TerminalRecorder { private _entries: RecorderEntry[]; - private _totalDataLength: number; + private _totalDataLength: number = 0; constructor(cols: number, rows: number) { this._entries = [{ cols, rows, data: [] }]; - this._totalDataLength = 0; } public recordResize(cols: number, rows: number): void { diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index d5f83c85257..3ec789f2b4b 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -5,6 +5,7 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; +import * as process from 'vs/base/common/process'; import { exists } from 'vs/base/node/pfs'; import { isString } from 'vs/base/common/types'; import { getCaseInsensitive } from 'vs/base/common/objects'; diff --git a/src/vs/platform/terminal/test/common/terminalRecorder.test.ts b/src/vs/platform/terminal/test/common/terminalRecorder.test.ts new file mode 100644 index 00000000000..007ce51448d --- /dev/null +++ b/src/vs/platform/terminal/test/common/terminalRecorder.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; +import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess'; + +function eventsEqual(recorder: TerminalRecorder, expected: ReplayEntry[]) { + const actual = recorder.generateReplayEvent().events; + for (let i = 0; i < expected.length; i++) { + assert.deepStrictEqual(actual[i], expected[i]); + } +} + +suite('TerminalRecorder', () => { + test('should record dimensions', () => { + const recorder = new TerminalRecorder(1, 2); + eventsEqual(recorder, [ + { cols: 1, rows: 2, data: '' } + ]); + recorder.recordData('a'); + recorder.recordResize(3, 4); + eventsEqual(recorder, [ + { cols: 1, rows: 2, data: 'a' }, + { cols: 3, rows: 4, data: '' } + ]); + }); + test('should ignore resize events without data', () => { + const recorder = new TerminalRecorder(1, 2); + eventsEqual(recorder, [ + { cols: 1, rows: 2, data: '' } + ]); + recorder.recordResize(3, 4); + eventsEqual(recorder, [ + { cols: 3, rows: 4, data: '' } + ]); + }); + test('should record data and combine it into the previous resize event', () => { + const recorder = new TerminalRecorder(1, 2); + recorder.recordData('a'); + recorder.recordData('b'); + recorder.recordResize(3, 4); + recorder.recordData('c'); + recorder.recordData('d'); + eventsEqual(recorder, [ + { cols: 1, rows: 2, data: 'ab' }, + { cols: 3, rows: 4, data: 'cd' } + ]); + }); +}); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 2d96ce768ad..29e938df2ef 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -7,15 +7,15 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { IRequestService } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -export function createUpdateURL(platform: string, quality: string): string { - return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`; +export function createUpdateURL(platform: string, quality: string, productService: IProductService): string { + return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`; } export type UpdateNotAvailableClassification = { @@ -49,6 +49,7 @@ export abstract class AbstractUpdateService implements IUpdateService { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IRequestService protected requestService: IRequestService, @ILogService protected logService: ILogService, + @IProductService protected readonly productService: IProductService ) { } /** @@ -66,7 +67,7 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - if (!product.updateUrl || !product.commit) { + if (!this.productService.updateUrl || !this.productService.commit) { this.logService.info('update#ctor - updates are disabled as there is no update URL'); return; } @@ -104,7 +105,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } private getProductQuality(updateMode: string): string | undefined { - return updateMode === 'none' ? undefined : product.quality; + return updateMode === 'none' ? undefined : this.productService.quality; } private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 8d67a459903..b876e8814dd 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -15,7 +15,7 @@ import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/e import { ILogService } from 'vs/platform/log/common/log'; import { AbstractUpdateService, createUpdateURL, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService } from 'vs/platform/request/common/request'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export class DarwinUpdateService extends AbstractUpdateService { @@ -34,9 +34,10 @@ export class DarwinUpdateService extends AbstractUpdateService { @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentMainService environmentMainService: IEnvironmentMainService, @IRequestService requestService: IRequestService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IProductService productService: IProductService ) { - super(lifecycleMainService, configurationService, environmentMainService, requestService, logService); + super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } initialize(): void { @@ -58,12 +59,12 @@ export class DarwinUpdateService extends AbstractUpdateService { protected buildUpdateFeedUrl(quality: string): string | undefined { let assetID: string; - if (!product.darwinUniversalAssetId) { + if (!this.productService.darwinUniversalAssetId) { assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; } else { - assetID = product.darwinUniversalAssetId; + assetID = this.productService.darwinUniversalAssetId; } - const url = createUpdateURL(assetID, quality); + const url = createUpdateURL(assetID, quality, this.productService); try { electron.autoUpdater.setFeedURL({ url }); } catch (e) { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index d00055b590f..7387636bfe7 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { State, IUpdate, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; @@ -26,13 +26,14 @@ export class LinuxUpdateService extends AbstractUpdateService { @IEnvironmentMainService environmentMainService: IEnvironmentMainService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProductService productService: IProductService ) { - super(lifecycleMainService, configurationService, environmentMainService, requestService, logService); + super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } protected buildUpdateFeedUrl(quality: string): string { - return createUpdateURL(`linux-${process.arch}`, quality); + return createUpdateURL(`linux-${process.arch}`, quality, this.productService); } protected doCheckForUpdates(context: any): void { @@ -64,8 +65,8 @@ export class LinuxUpdateService extends AbstractUpdateService { protected async doDownloadUpdate(state: AvailableForDownload): Promise { // Use the download URL if available as we don't currently detect the package type that was // installed and the website download page is more useful than the tarball generally. - if (product.downloadUrl && product.downloadUrl.length > 0) { - this.nativeHostMainService.openExternal(undefined, product.downloadUrl); + if (this.productService.downloadUrl && this.productService.downloadUrl.length > 0) { + this.nativeHostMainService.openExternal(undefined, this.productService.downloadUrl); } else if (state.update.url) { this.nativeHostMainService.openExternal(undefined, state.update.url); } diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 3fcc37e91d4..62d82342b36 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -9,7 +9,7 @@ import * as pfs from 'vs/base/node/pfs'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -55,7 +55,7 @@ export class Win32UpdateService extends AbstractUpdateService { @memoize get cachePath(): Promise { - const result = path.join(tmpdir(), `vscode-update-${product.target}-${process.arch}`); + const result = path.join(tmpdir(), `vscode-update-${this.productService.target}-${process.arch}`); return fs.promises.mkdir(result, { recursive: true }).then(() => result); } @@ -67,9 +67,10 @@ export class Win32UpdateService extends AbstractUpdateService { @IRequestService requestService: IRequestService, @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProductService productService: IProductService ) { - super(lifecycleMainService, configurationService, environmentMainService, requestService, logService); + super(lifecycleMainService, configurationService, environmentMainService, requestService, logService, productService); } initialize(): void { @@ -86,7 +87,7 @@ export class Win32UpdateService extends AbstractUpdateService { "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('update:win32SetupTarget', { target: product.target }); + this.telemetryService.publicLog('update:win32SetupTarget', { target: this.productService.target }); } } @@ -99,11 +100,11 @@ export class Win32UpdateService extends AbstractUpdateService { if (getUpdateType() === UpdateType.Archive) { platform += '-archive'; - } else if (product.target === 'user') { + } else if (this.productService.target === 'user') { platform += '-user'; } - return createUpdateURL(platform, quality); + return createUpdateURL(platform, quality, this.productService); } protected doCheckForUpdates(context: any): void { @@ -155,7 +156,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.availableUpdate = { packagePath }; if (fastUpdatesEnabled && update.supportsFastUpdate) { - if (product.target === 'user') { + if (this.productService.target === 'user') { this.doApplyUpdate(); } else { this.setState(State.Downloaded(update)); @@ -185,11 +186,11 @@ export class Win32UpdateService extends AbstractUpdateService { private async getUpdatePackagePath(version: string): Promise { const cachePath = await this.cachePath; - return path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`); + return path.join(cachePath, `CodeSetup-${this.productService.quality}-${version}.exe`); } private async cleanup(exceptVersion: string | null = null): Promise { - const filter = exceptVersion ? (one: string) => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true; + const filter = exceptVersion ? (one: string) => !(new RegExp(`${this.productService.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true; const cachePath = await this.cachePath; const versions = await pfs.readdir(cachePath); @@ -219,7 +220,7 @@ export class Win32UpdateService extends AbstractUpdateService { const cachePath = await this.cachePath; - this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${update.version}.flag`); + this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${this.productService.quality}-${update.version}.flag`); await pfs.writeFile(this.availableUpdate.updateFilePath, 'flag'); const child = spawn(this.availableUpdate.packagePath, ['/verysilent', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { @@ -233,7 +234,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.setState(State.Idle(getUpdateType())); }); - const readyMutexName = `${product.win32MutexName}-ready`; + const readyMutexName = `${this.productService.win32MutexName}-ready`; const mutex = await import('windows-mutex'); // poll for mutex-ready diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index 0c1e354c073..e096b96c3c1 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -7,7 +7,7 @@ import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/commo import { URI, UriComponents } from 'vs/base/common/uri'; import { first } from 'vs/base/common/async'; import { toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export abstract class AbstractURLService extends Disposable implements IURLService { @@ -30,6 +30,12 @@ export abstract class AbstractURLService extends Disposable implements IURLServi export class NativeURLService extends AbstractURLService { + constructor( + @IProductService protected readonly productService: IProductService + ) { + super(); + } + create(options?: Partial): URI { let { authority, path, query, fragment } = options ? options : { authority: undefined, path: undefined, query: undefined, fragment: undefined }; @@ -37,6 +43,6 @@ export class NativeURLService extends AbstractURLService { path = `/${path}`; // URI validation requires a path if there is an authority } - return URI.from({ scheme: product.urlProtocol, authority, path, query, fragment }); + return URI.from({ scheme: this.productService.urlProtocol, authority, path, query, fragment }); } } diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index e281cff7d5e..0c0841ebfd5 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { app, Event as ElectronEvent } from 'electron'; import { URI } from 'vs/base/common/uri'; import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -43,7 +43,8 @@ export class ElectronURLListener { initialUrisToHandle: { uri: URI, url: string }[], private readonly urlService: IURLService, windowsMainService: IWindowsMainService, - environmentMainService: IEnvironmentMainService + environmentMainService: IEnvironmentMainService, + productService: IProductService ) { // the initial set of URIs we need to handle once the window is ready @@ -53,7 +54,7 @@ export class ElectronURLListener { if (isWindows) { const windowsParameters = environmentMainService.isBuilt ? [] : [`"${environmentMainService.appRoot}"`]; windowsParameters.push('--open-url', '--'); - app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, windowsParameters); + app.setAsDefaultProtocolClient(productService.urlProtocol, process.execPath, windowsParameters); } // macOS: listen to `open-url` events from here on to handle diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 0124ffeab7d..2931ef5e051 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -118,6 +118,7 @@ export abstract class AbstractSynchroniser extends Disposable { readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; protected readonly lastSyncResource: URI; + private hasSyncResourceStateVersionChanged: boolean = false; protected readonly syncResourceLogLabel: string; private syncHeaders: IHeaders = {}; @@ -566,8 +567,7 @@ export abstract class AbstractSynchroniser extends Disposable { const machineId = await this.currentMachineIdPromise; const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId; - // For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine - const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData; + const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine && !this.hasSyncResourceStateVersionChanged ? remoteUserData : lastSyncUserData; const resourcePreviewResults = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token); const resourcePreviews: IEditableResourcePreview[] = []; @@ -616,6 +616,14 @@ export abstract class AbstractSynchroniser extends Disposable { try { const content = await this.fileService.readFile(this.lastSyncResource); const parsed = JSON.parse(content.value.toString()); + const resourceSyncStateVersion = this.userDataSyncResourceEnablementService.getResourceSyncStateVersion(this.resource); + this.hasSyncResourceStateVersionChanged = parsed.version && resourceSyncStateVersion && parsed.version !== resourceSyncStateVersion; + if (this.hasSyncResourceStateVersionChanged) { + this.logService.info(`${this.syncResourceLogLabel}: Reset last sync state because last sync state version ${parsed.version} is not compatible with current sync state version ${resourceSyncStateVersion}.`); + await this.resetLocal(); + return null; + } + const userData: IUserData = parsed as IUserData; if (userData.content === null) { return { ref: parsed.ref, syncData: null } as T; @@ -637,7 +645,12 @@ export abstract class AbstractSynchroniser extends Disposable { } protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary = {}): Promise { - const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, ...additionalProps }; + if (additionalProps['ref'] || additionalProps['content'] || additionalProps['version']) { + throw new Error('Cannot have core properties as additional'); + } + + const version = this.userDataSyncResourceEnablementService.getResourceSyncStateVersion(this.resource); + const lastSyncUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, version, ...additionalProps }; await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index d34583109a8..f15de01dc95 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -408,6 +408,8 @@ export interface IUserDataSyncResourceEnablementService { readonly onDidChangeResourceEnablement: Event<[SyncResource, boolean]>; isResourceEnabled(resource: SyncResource): boolean; setResourceEnablement(resource: SyncResource, enabled: boolean): void; + + getResourceSyncStateVersion(resource: SyncResource): string | undefined; } export interface ISyncTask { diff --git a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts index c96fbad455b..93ae033bd5f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncResourceEnablementService.ts @@ -41,6 +41,10 @@ export class UserDataSyncResourceEnablementService extends Disposable implements } } + getResourceSyncStateVersion(resource: SyncResource): string | undefined { + return undefined; + } + private storeResourceEnablement(resourceEnablementKey: string, enabled: boolean): void { this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, isWeb ? StorageTarget.USER /* sync in web */ : StorageTarget.MACHINE); } diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index 0eebc9dc8be..6b1469925fe 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { WindowMinimumSize, IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; import { browserCodeLoadingCacheStrategy, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -119,7 +119,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ITelemetryService private readonly telemetryService: ITelemetryService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService + @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, + @IProductService private readonly productService: IProductService ) { super(); @@ -144,7 +145,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { minWidth: WindowMinimumSize.WIDTH, minHeight: WindowMinimumSize.HEIGHT, show: !isFullscreenOrMaximized, - title: product.nameLong, + title: this.productService.nameLong, webPreferences: { preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload.js', require).fsPath, v8CacheOptions: browserCodeLoadingCacheStrategy, @@ -196,7 +197,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { const useNativeTabs = isMacintosh && windowConfig?.nativeTabs === true; if (useNativeTabs) { - options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs + options.tabbingIdentifier = this.productService.nameShort; // this opts in to sierra tabs } const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom'; @@ -263,7 +264,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentMainService, this.fileService, { + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.environmentMainService, this.fileService, { get: key => storageMainService.globalStorage.get(key), store: (key, value) => storageMainService.globalStorage.set(key, value) }); @@ -575,7 +576,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Show Dialog const result = await this.dialogMainService.showMessageBox({ - title: product.nameLong, + title: this.productService.nameLong, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message: localize('appStalled', "The window is no longer responding"), @@ -605,7 +606,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } const result = await this.dialogMainService.showMessageBox({ - title: product.nameLong, + title: this.productService.nameLong, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message, @@ -731,7 +732,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.setRepresentedFilename(''); } - this._win.setTitle(product.nameLong); + this._win.setTitle(this.productService.nameLong); } // Load URL diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index c9f7b5c2a83..283bbc93c80 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -20,7 +20,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration, INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; import { findWindowOnFile, findWindowOnWorkspaceOrFolder, findWindowOnExtensionDevelopmentPath } from 'vs/platform/windows/electron-main/windowsFinder'; import { Emitter } from 'vs/base/common/event'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IOpenEmptyConfiguration, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; @@ -41,6 +41,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { getPathLabel } from 'vs/base/common/labels'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IFileService } from 'vs/platform/files/common/files'; +import { cwd } from 'vs/base/common/process'; //#region Helper Interfaces @@ -149,7 +150,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogMainService private readonly dialogMainService: IDialogMainService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IProductService private readonly productService: IProductService ) { super(); @@ -665,7 +667,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic const uri = this.resourceFromOpenable(pathToOpen); const options: MessageBoxOptions = { - title: product.nameLong, + title: this.productService.nameLong, type: 'info', buttons: [localize('ok', "OK")], message: uri.scheme === Schemas.file ? localize('pathNotExistTitle', "Path does not exist") : localize('uriInvalidTitle', "URI can not be opened"), @@ -884,7 +886,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Ensure the path is normalized and absolute - path = sanitizeFilePath(normalize(path), process.env['VSCODE_CWD'] || process.cwd()); + path = sanitizeFilePath(normalize(path), cwd()); try { const pathStat = statSync(path); diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts index 1fd596212bd..8a1b2999b6e 100644 --- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts +++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts @@ -9,7 +9,7 @@ import { findWindowOnFile } from 'vs/platform/windows/electron-main/windowsFinde import { ICodeWindow, ILoadEvent, IWindowState } from 'vs/platform/windows/electron-main/windows'; import { IWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/workspace/common/workspaceTrust.ts b/src/vs/platform/workspace/common/workspaceTrust.ts index 47075c714df..74bc4625793 100644 --- a/src/vs/platform/workspace/common/workspaceTrust.ts +++ b/src/vs/platform/workspace/common/workspaceTrust.ts @@ -44,7 +44,14 @@ export interface IWorkspaceTrustModel { getTrustStateInfo(): IWorkspaceTrustStateInfo; } +export interface WorkspaceTrustRequestButton { + label: string; + type: 'ContinueWithTrust' | 'ContinueWithoutTrust' | 'Manage' | 'Cancel' +} + export interface WorkspaceTrustRequest { + buttons?: WorkspaceTrustRequestButton[]; + message?: string; modal: boolean; } @@ -53,9 +60,11 @@ export interface IWorkspaceTrustRequestModel { readonly onDidInitiateRequest: Event; readonly onDidCompleteRequest: Event; + readonly onDidCancelRequest: Event; initiateRequest(request?: WorkspaceTrustRequest): void; completeRequest(trustState?: WorkspaceTrustState): void; + cancelRequest(): void; } export interface WorkspaceTrustStateChangeEvent { diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index 5839eb53100..4a510b7ffb8 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -20,7 +20,7 @@ import { originalFSPath, joinPath, basename, extUriBiasedIgnorePathCase } from ' import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { localize } from 'vs/nls'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { MessageBoxOptions, BrowserWindow } from 'electron'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -77,7 +77,8 @@ export class WorkspacesManagementMainService extends Disposable implements IWork @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, @IBackupMainService private readonly backupMainService: IBackupMainService, - @IDialogMainService private readonly dialogMainService: IDialogMainService + @IDialogMainService private readonly dialogMainService: IDialogMainService, + @IProductService private readonly productService: IProductService ) { super(); } @@ -270,7 +271,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork // Prevent overwriting a workspace that is currently opened in another window if (findWindowOnWorkspaceOrFolder(windows, workspacePath)) { const options: MessageBoxOptions = { - title: product.nameLong, + title: this.productService.nameLong, type: 'info', buttons: [localize('ok', "OK")], message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspacePath)), 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 914f1f13a08..c218f74c847 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -22,6 +22,8 @@ import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMain import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; +import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; suite('WorkspacesManagementMainService', () => { @@ -92,10 +94,12 @@ suite('WorkspacesManagementMainService', () => { testDir = getRandomTestPath(tmpDir, 'vsctests', 'workspacesmanagementmainservice'); untitledWorkspacesHomePath = path.join(testDir, 'Workspaces'); + const productService: IProductService = { _serviceBrand: undefined, ...product }; + environmentMainService = new class TestEnvironmentService extends EnvironmentMainService { constructor() { - super(parseArgs(process.argv, OPTIONS)); + super(parseArgs(process.argv, OPTIONS), productService); } get untitledWorkspacesHome(): URI { @@ -103,7 +107,7 @@ suite('WorkspacesManagementMainService', () => { } }; - service = new WorkspacesManagementMainService(environmentMainService, new NullLogService(), new TestBackupMainService(), new TestDialogMainService()); + service = new WorkspacesManagementMainService(environmentMainService, new NullLogService(), new TestBackupMainService(), new TestDialogMainService(), productService); return fs.promises.mkdir(untitledWorkspacesHomePath, { recursive: true }); }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 853cf4da426..ac88f7bf9ee 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2668,6 +2668,134 @@ declare module 'vscode' { provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } + /** + * 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); + } + + /** + * 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; + + /** + * A value-object that contains contextual information when requesting inline values from a InlineValuesProvider. + */ + export interface InlineValueContext { + + /** + * The stack frame (as a DAP Id) where the execution has stopped. + */ + readonly frameId: number; + + /** + * The document range where execution has stopped. + * Typically the end position of the range denotes the line where the inline values are shown. + */ + readonly stoppedLocation: Range; + } + + /** + * 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 document highlight kind. */ @@ -3236,7 +3364,7 @@ declare module 'vscode' { appendPlaceholder(value: string | ((snippet: SnippetString) => any), number?: number): SnippetString; /** - * Builder-function that appends a choice (`${1|a,b,c}`) to + * Builder-function that appends a choice (`${1|a,b,c|}`) to * the [`value`](#SnippetString.value) of this snippet string. * * @param values The values for choices - the array of strings @@ -10787,6 +10915,21 @@ declare module 'vscode' { */ export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + /** + * 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; + /** * Register a document highlight provider. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ef11faaae1c..4a90f0d8647 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -664,156 +664,6 @@ 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 contextual information when requesting inline values from a InlineValuesProvider. - */ - export interface InlineValueContext { - - /** - * The stack frame (as a DAP Id) where the execution has stopped. - */ - readonly frameId: number; - - /** - * The document range where execution has stopped. - * Typically the end position of the range denotes the line where the inline values are shown. - */ - readonly stoppedLocation: Range; - } - - /** - * 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 @weinand: variables view action contributions @@ -1270,7 +1120,7 @@ declare module 'vscode' { readonly document: NotebookDocument; /** - * The primary selected cell on this notebook editor. + * @deprecated */ // todo@API should not be undefined, rather a default readonly selection?: NotebookCell; @@ -1298,7 +1148,7 @@ declare module 'vscode' { readonly viewColumn?: ViewColumn; /** - * Fired when the panel is disposed. + * @deprecated */ // @rebornix REMOVE/REplace NotebookCommunication // todo@API fishy? notebooks are public objects, there should be a "global" events for this @@ -1460,6 +1310,8 @@ declare module 'vscode' { // code specific mime types // application/x.notebook.error-traceback + // application/x.notebook.stdout + // application/x.notebook.stderr // application/x.notebook.stream export class NotebookCellOutputItem { @@ -1568,6 +1420,9 @@ declare module 'vscode' { readonly onDidChangeNotebookContentOptions?: Event; // todo@API remove! against separation of data provider and renderer + /** + * @deprecated + */ // eslint-disable-next-line vscode-dts-cancellation resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable; @@ -2817,18 +2672,18 @@ declare module 'vscode' { * The reason why code actions were requested. */ export enum CodeActionTriggerKind { + /** + * Code actions were explicitly requested by the user or by an extension. + */ + Invoke = 1, + /** * Code actions were requested automatically. * * This typically happens when current selection in a file changes, but can * also be triggered when file content changes. */ - Automatic = 1, - - /** - * Code actions were requested maually by the user or an extension. - */ - Manual = 2, + Automatic = 2, } export interface CodeActionContext { @@ -2854,6 +2709,11 @@ declare module 'vscode' { } export interface PortAttributesProvider { + /** + * Provides attributes for the given ports. For ports that your extension doesn't know about, simply don't include + * them in the returned array. For example, if `providePortAttributes` is called with ports [3000, 4000] but your + * extension doesn't know anything about those ports you can return an empty array. + */ providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; } @@ -2866,6 +2726,7 @@ declare module 'vscode' { * * @param portSelector If registerPortAttributesProvider is called after you start your process then you may already * know the range of ports or the pid of your process. + * The `portRange` is start inclusive and end exclusive. * @param provider The PortAttributesProvider */ export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: PortAttributesProvider): Disposable; diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index ec724cb5309..287bcd08305 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -142,7 +142,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } else if (dto.type === 'function') { this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes); + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes, dto.accessType); } } return Promise.resolve(); diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index a847df3bd0d..246982f9bc3 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -15,7 +15,7 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce export class MainThreadStatusBar implements MainThreadStatusBarShape { private readonly entries: Map = new Map(); - static readonly CODICON_REGEXP = /\$\((.*?)\)/g; + private static readonly CODICON_REGEXP = /\$\((.*?)\)/g; constructor( _extHostContext: IExtHostContext, diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index bfe986601f1..b049f8646d8 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -15,7 +15,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITerminalExternalLinkProvider, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; -import { IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IAvailableProfilesRequest as IAvailableProfilesRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest, ITerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) @@ -66,7 +66,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null))); this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => instance && this._onTitleChanged(instance.instanceId, instance.title))); this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed))); - this._toDispose.add(_terminalService.onRequestAvailableShells(e => this._onRequestAvailableShells(e))); + this._toDispose.add(_terminalService.onRequestAvailableProfiles(e => this._onRequestAvailableProfiles(e))); // ITerminalInstanceService listeners if (terminalInstanceService.onRequestDefaultShellAndArgs) { @@ -349,9 +349,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape return true; } - private async _onRequestAvailableShells(req: IAvailableShellsRequest): Promise { + private async _onRequestAvailableProfiles(req: IAvailableProfilesRequest): Promise { if (this._isPrimaryExtHost()) { - req.callback(await this._proxy.$getAvailableShells()); + req.callback(await this._proxy.$getAvailableProfiles(req.quickLaunchOnly)); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index fd84b560c25..d4bc6efc9eb 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -425,7 +425,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I 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 { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index aaa24b06bd7..9e2f8e7b39e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -61,6 +61,7 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplore import { WorkspaceTrustRequest, WorkspaceTrustStateChangeEvent } from 'vs/platform/workspace/common/workspaceTrust'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; +import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -1609,11 +1610,6 @@ export interface IShellLaunchConfigDto { hideFromUser?: boolean; } -export interface IShellDefinitionDto { - label: string; - path: string; -} - export interface IShellAndArgsDto { shell: string; args: string[] | string | undefined; @@ -1653,7 +1649,7 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestCwd(id: number): void; $acceptProcessRequestLatency(id: number): number; $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; - $getAvailableShells(): Promise; + $getAvailableProfiles(quickLaunchOnly: boolean): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; $provideLinks(id: number, line: string): Promise; $activateLink(id: number, linkId: number): void; @@ -1701,6 +1697,7 @@ export interface IDataBreakpointDto extends IBreakpointDto { canPersist: boolean; label: string; accessTypes?: DebugProtocol.DataBreakpointAccessType[]; + accessType: DebugProtocol.DataBreakpointAccessType; } export interface ISourceBreakpointDto extends IBreakpointDto { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 25673875326..57fb74725b0 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -30,7 +30,7 @@ import type * as vscode from 'vscode'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import * as process from 'vs/base/common/process'; export const IExtHostDebugService = createDecorator('IExtHostDebugService'); @@ -930,7 +930,7 @@ export class ExtHostDebugConsole { export class ExtHostVariableResolverService extends AbstractVariableResolverService { - constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, env?: IProcessEnvironment, workspaceService?: IExtHostWorkspace) { + constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors | undefined, configurationService: ExtHostConfigProvider, workspaceService?: IExtHostWorkspace) { super({ getFolderUri: (folderName: string): URI | undefined => { const found = folders.filter(f => f.name === folderName); @@ -946,10 +946,10 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ return configurationService.getConfiguration(undefined, folderUri).get(section); }, getAppRoot: (): string | undefined => { - return env ? env['VSCODE_CWD'] : undefined; + return process.cwd(); }, getExecPath: (): string | undefined => { - return env ? env['VSCODE_EXEC_PATH'] : undefined; + return process.env['VSCODE_EXEC_PATH']; }, getFilePath: (): string | undefined => { if (editorService) { @@ -990,7 +990,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ } return undefined; } - }, undefined, env); + }, undefined, process.env); } } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index e670cfb4209..8a8b40bca9a 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -547,12 +547,14 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme ); } - public $extensionTestsExecute(): Promise { - return this._doHandleExtensionTests().then(undefined, error => { + public async $extensionTestsExecute(): Promise { + await this._readyToRunExtensions.wait(); + try { + return this._doHandleExtensionTests(); + } catch (error) { console.error(error); // ensure any error message makes it onto the console - - return Promise.reject(error); - }); + throw error; + } } private async _doHandleExtensionTests(): Promise { diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index d7db691c043..0d32f885bd7 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -16,6 +16,7 @@ import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/common/extHostTyp import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; +import Severity from 'vs/base/common/severity'; export type Item = string | QuickPickItem; @@ -632,7 +633,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx set validationMessage(validationMessage: string | undefined) { this._validationMessage = validationMessage; - this.update({ validationMessage }); + this.update({ validationMessage, severity: validationMessage ? Severity.Error : Severity.Ignore }); } } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 75a2229767a..8c34992d106 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -5,7 +5,7 @@ import type * as vscode from 'vscode'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellAndArgsDto, ITerminalDimensionsDto, ITerminalLinkDto, TerminalIdentifier } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; @@ -21,6 +21,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering'; +import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -327,7 +328,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public abstract createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal; public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; - public abstract $getAvailableShells(): Promise; + public abstract $getAvailableProfiles(quickLaunchOnly: boolean): Promise; public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; @@ -778,7 +779,7 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { throw new NotSupportedError(); } - public $getAvailableShells(): Promise { + public $getAvailableProfiles(quickLaunchOnly: boolean): Promise { throw new NotSupportedError(); } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index fe2aec8f49a..4691259728e 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1731,11 +1731,11 @@ export namespace CodeActionTriggerKind { export function to(value: modes.CodeActionTriggerType): types.CodeActionTriggerKind { switch (value) { + case modes.CodeActionTriggerType.Invoke: + return types.CodeActionTriggerKind.Invoke; + case modes.CodeActionTriggerType.Auto: return types.CodeActionTriggerKind.Automatic; - - case modes.CodeActionTriggerType.Manual: - return types.CodeActionTriggerKind.Manual; } } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 88833b1730f..bef6a7bf01c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1179,8 +1179,8 @@ export class DocumentSymbol { export enum CodeActionTriggerKind { - Automatic = 1, - Manual = 2, + Invoke = 1, + Automatic = 2, } @es5ClassCompat diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 1303242e0ad..c1e8000031b 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -53,6 +53,11 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.EditorContext, description: localize('menus.editorContext', "The editor context menu") }, + { + key: 'editor/context/copy', + id: MenuId.EditorContextCopy, + description: localize('menus.editorContextCopyAs', "'Copy as' submenu in the editor context menu") + }, { key: 'explorer/context', id: MenuId.ExplorerContext, @@ -91,6 +96,11 @@ const apiMenus: IAPIMenu[] = [ proposed: true, supportsSubmenus: false }, + { + key: 'menuBar/edit/copy', + id: MenuId.MenubarCopy, + description: localize('menus.opy', "'Copy as' submenu in the top level Edit menu") + }, { key: 'scm/title', id: MenuId.SCMTitle, diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index de3c7c3df6a..c8c7f4cd50c 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import type * as vscode from 'vscode'; -import * as env from 'vs/base/common/platform'; +import * as platform from 'vs/base/common/platform'; import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; @@ -110,10 +110,23 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { if (giveShellTimeToInitialize) { // give a new terminal some time to initialize the shell await new Promise(resolve => setTimeout(resolve, 1000)); + } else { + if (configProvider.getConfiguration('debug.terminal').get('clearBeforeReusing')) { + // clear terminal before reusing it + if (shell.indexOf('powershell') >= 0 || shell.indexOf('pwsh') >= 0 || shell.indexOf('cmd.exe') >= 0) { + terminal.sendText('cls'); + } else if (shell.indexOf('bash') >= 0) { + terminal.sendText('clear'); + } else if (platform.isWindows) { + terminal.sendText('cls'); + } else { + terminal.sendText('clear'); + } + } } const command = prepareCommand(shell, args.args, cwdForPrepareCommand, args.env); - terminal.sendText(command, true); + terminal.sendText(command); // Mark terminal as unused when its session ends, see #112055 const sessionListener = this.onDidTerminateDebugSession(s => { @@ -133,7 +146,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { } protected createVariableResolver(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider): AbstractVariableResolverService { - return new ExtHostVariableResolverService(folders, editorService, configurationService, process.env as env.IProcessEnvironment, this._workspaceService); + return new ExtHostVariableResolverService(folders, editorService, configurationService, this._workspaceService); } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 260ace16bd9..54b50a8b152 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -22,7 +22,6 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecutionDTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; import { Schemas } from 'vs/base/common/network'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; export class ExtHostTask extends ExtHostTaskBase { @@ -122,7 +121,7 @@ export class ExtHostTask extends ExtHostTaskBase { private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise { if (this._variableResolver === undefined) { const configProvider = await this._configurationService.getConfigProvider(); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment, this.workspaceService); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, this.workspaceService); } return this._variableResolver; } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index a799d988111..930b010a09c 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -9,15 +9,16 @@ import { generateUuid } from 'vs/base/common/uuid'; import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IShellAndArgsDto, IShellDefinitionDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfigProvider, ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ITerminalConfiguration, ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; +import { detectAvailableProfiles } from 'vs/workbench/contrib/terminal/node/terminalProfiles'; import type * as vscode from 'vscode'; export class ExtHostTerminalService extends BaseExtHostTerminalService { @@ -128,11 +129,12 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { private async _updateVariableResolver(): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); - this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider, process.env as platform.IProcessEnvironment); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); } - public $getAvailableShells(): Promise { - return detectAvailableShells(); + public async $getAvailableProfiles(quickLaunchOnly: boolean): Promise { + const config = await (await this._extHostConfiguration.getConfigProvider()).getConfiguration().get('terminal.integrated'); + return detectAvailableProfiles(quickLaunchOnly, this._logService, config as ITerminalConfiguration, this._variableResolver, this._lastActiveWorkspace); } public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise { diff --git a/src/vs/workbench/browser/parts/editor/untitledHint.ts b/src/vs/workbench/browser/parts/editor/untitledHint.ts index ce498d0aca6..f06f57d79fc 100644 --- a/src/vs/workbench/browser/parts/editor/untitledHint.ts +++ b/src/vs/workbench/browser/parts/editor/untitledHint.ts @@ -29,7 +29,7 @@ export class UntitledHintContribution implements IEditorContribution { private toDispose: IDisposable[]; private untitledHintContentWidget: UntitledHintContentWidget | undefined; private button: FloatingClickWidget | undefined; - private experimentTreatment: 'text' | 'button' | undefined; + private experimentTreatment: 'text' | 'button' | 'hidden' | undefined; constructor( private editor: ICodeEditor, @@ -56,8 +56,8 @@ export class UntitledHintContribution implements IEditorContribution { private update(): void { this.untitledHintContentWidget?.dispose(); this.button?.dispose(); - const configValue = this.configurationService.getValue<'text' | 'button' | 'hidden'>(untitledHintSetting); - const untitledHintMode = configValue === 'hidden' ? configValue : (this.experimentTreatment || configValue); + const configValue = this.configurationService.getValue<'text' | 'button' | 'hidden' | 'default'>(untitledHintSetting); + const untitledHintMode = configValue === 'default' ? (this.experimentTreatment || 'hidden') : configValue; const model = this.editor.getModel(); if (model && model.uri.scheme === Schemas.untitled && model.getModeId() === PLAINTEXT_MODE_ID) { diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 8bb30219d53..bc20eca2daf 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -594,6 +594,7 @@ export class CustomMenubarControl extends MenubarControl { const updateActions = (menu: IMenu, target: IAction[], topLevelTitle: string) => { target.splice(0); let groups = menu.getActions(); + for (let group of groups) { const [, actions] = group; @@ -622,7 +623,10 @@ export class CustomMenubarControl extends MenubarControl { const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions, topLevelTitle); - target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions)); + + if (submenuActions.length > 0) { + target.push(new SubmenuAction(action.id, mnemonicMenuLabel(title), submenuActions)); + } } else { const newAction = new Action(action.id, mnemonicMenuLabel(title), action.class, action.enabled, () => this.commandService.executeCommand(action.id)); newAction.tooltip = action.tooltip; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 6a06c42c093..e0142481b4b 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -88,8 +88,8 @@ import { isStandalone } from 'vs/base/browser/browser'; }, 'workbench.editor.untitled.hint': { 'type': 'string', - 'enum': ['text', 'button', 'hidden'], - 'default': 'hidden', + 'enum': ['text', 'button', 'hidden', 'default'], + 'default': 'default', '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': { diff --git a/src/vs/workbench/contrib/cli/node/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts index 74293fcf252..dcc7623b08a 100644 --- a/src/vs/workbench/contrib/cli/node/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -111,12 +111,17 @@ class InstallAction extends Action2 { }); } - private isInstalled(target: string): Promise { - return fs.promises.lstat(target) - .then(stat => stat.isSymbolicLink()) - .then(() => extpath.realpath(target)) - .then(link => link === getSource()) - .then(undefined, ignore('ENOENT', false)); + private async isInstalled(target: string): Promise { + try { + const stat = await fs.promises.lstat(target); + return stat.isSymbolicLink() && getSource() === await extpath.realpath(target); + } catch (err) { + if (err.code === 'ENOENT') { + return false; + } + + throw err; + } } } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index ce84ce7cf34..c2335024ea2 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -16,7 +16,7 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; @@ -32,7 +32,7 @@ import { launchSchemaId } from 'vs/workbench/services/configuration/common/confi import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView'; import { RunToCursorAction } from 'vs/workbench/contrib/debug/browser/debugEditorActions'; import { WatchExpressionsView, ADD_WATCH_LABEL, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, ADD_WATCH_ID } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; -import { VariablesView, SET_VARIABLE_ID, COPY_VALUE_ID, BREAK_WHEN_VALUE_CHANGES_ID, COPY_EVALUATE_PATH_ID, ADD_TO_WATCH_ID } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { VariablesView, SET_VARIABLE_ID, COPY_VALUE_ID, BREAK_WHEN_VALUE_CHANGES_ID, COPY_EVALUATE_PATH_ID, ADD_TO_WATCH_ID, BREAK_WHEN_VALUE_IS_ACCESSED_ID, BREAK_WHEN_VALUE_IS_READ_ID } from 'vs/workbench/contrib/debug/browser/variablesView'; import { Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView'; @@ -138,7 +138,9 @@ registerDebugViewMenuItem(MenuId.DebugVariablesContext, SET_VARIABLE_ID, nls.loc registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_VALUE_ID, nls.localize('copyValue', "Copy Value"), 10, undefined, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, COPY_EVALUATE_PATH_ID, nls.localize('copyAsExpression', "Copy as Expression"), 20, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, '5_cutcopypaste'); registerDebugViewMenuItem(MenuId.DebugVariablesContext, ADD_TO_WATCH_ID, nls.localize('addToWatchExpressions', "Add to Watch"), 100, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, undefined, 'z_commands'); -registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_CHANGES_ID, nls.localize('breakWhenValueChanges', "Break When Value Changes"), 200, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, undefined, 'z_commands'); +registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_IS_READ_ID, nls.localize('breakWhenValueIsRead', "Break When Value Is Read"), 200, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, undefined, 'z_commands'); +registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_CHANGES_ID, nls.localize('breakWhenValueChanges', "Break When Value Changes"), 210, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, undefined, 'z_commands'); +registerDebugViewMenuItem(MenuId.DebugVariablesContext, BREAK_WHEN_VALUE_IS_ACCESSED_ID, nls.localize('breakWhenValueIsAccessed', "Break When Value Is Accessed"), 220, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, undefined, 'z_commands'); registerDebugViewMenuItem(MenuId.DebugWatchContext, ADD_WATCH_ID, ADD_WATCH_LABEL, 10, undefined, undefined, '3_modification'); registerDebugViewMenuItem(MenuId.DebugWatchContext, EDIT_EXPRESSION_COMMAND_ID, nls.localize('editWatchExpression', "Edit Expression"), 20, CONTEXT_WATCH_ITEM_TYPE.isEqualTo('expression'), undefined, '3_modification'); @@ -398,6 +400,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('debug.console.closeOnEnd', "Controls if the debug console should be automatically closed when the debug session ends."), default: false }, + 'debug.terminal.clearBeforeReusing': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'debug.terminal.clearBeforeReusing' }, "Before starting a new debug session in an integrated or external terminal, clear the terminal."), + default: false + }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], default: 'openOnFirstSessionStart', diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 8101a3c16a9..4395af9429a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -30,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IViewsService } from 'vs/workbench/common/views'; import { deepClone } from 'vs/base/common/objects'; -import { isWeb } from 'vs/base/common/platform'; +import { isWeb, isWindows } from 'vs/base/common/platform'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint'; @@ -250,7 +250,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: STEP_INTO_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging - primary: KeyCode.F11, + primary: (isWeb && isWindows) ? (KeyMod.Alt | KeyCode.F11) : KeyCode.F11, // Windows browsers use F11 for full screen, thus use alt+F11 as the default shortcut // Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b4e66b54020..25a28ac5cbf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -924,8 +924,8 @@ export class DebugService implements IDebugService { await this.sendFunctionBreakpoints(); } - async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise { - this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes); + async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise { + this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes, accessType); this.debugStorage.storeBreakpoints(this.model); await this.sendDataBreakpoints(); this.debugStorage.storeBreakpoints(this.model); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 665894a0bad..537bf780c48 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -6,7 +6,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IStackFrame, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, IDataBreakpointInfoResponse, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, VARIABLES_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IStackFrame, CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT, IDataBreakpointInfoResponse, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, VARIABLES_VIEW_ID, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; import { Variable, Scope, ErrorScope, StackFrame, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -58,6 +58,8 @@ export class VariablesView extends ViewPane { private menu: IMenu; private debugProtocolVariableMenuContext: IContextKey; private breakWhenValueChangesSupported: IContextKey; + private breakWhenValueIsAccessedSupported: IContextKey; + private breakWhenValueIsReadSupported: IContextKey; private variableEvaluateName: IContextKey; constructor( @@ -80,6 +82,8 @@ export class VariablesView extends ViewPane { this._register(this.menu); this.debugProtocolVariableMenuContext = CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT.bindTo(contextKeyService); this.breakWhenValueChangesSupported = CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED.bindTo(contextKeyService); + this.breakWhenValueIsAccessedSupported = CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED.bindTo(contextKeyService); + this.breakWhenValueIsReadSupported = CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED.bindTo(contextKeyService); this.variableEvaluateName = CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT.bindTo(contextKeyService); // Use scheduler to prevent unnecessary flashing @@ -206,10 +210,30 @@ export class VariablesView extends ViewPane { const session = this.debugService.getViewModel().focusedSession; this.variableEvaluateName.set(!!variable.evaluateName); this.breakWhenValueChangesSupported.reset(); + this.breakWhenValueIsAccessedSupported.reset(); + this.breakWhenValueIsReadSupported.reset(); if (session && session.capabilities.supportsDataBreakpoints) { dataBreakpointInfoResponse = await session.dataBreakpointInfo(variable.name, variable.parent.reference); const dataBreakpointId = dataBreakpointInfoResponse?.dataId; - this.breakWhenValueChangesSupported.set(!!dataBreakpointId); + const dataBreakpointAccessTypes = dataBreakpointInfoResponse?.accessTypes; + if (!dataBreakpointAccessTypes) { + // Assumes default behaviour: Supports breakWhenValueChanges + this.breakWhenValueChangesSupported.set(!!dataBreakpointId); + } else { + dataBreakpointAccessTypes.forEach(accessType => { + switch (accessType) { + case 'read': + this.breakWhenValueIsReadSupported.set(!!dataBreakpointId); + break; + case 'write': + this.breakWhenValueChangesSupported.set(!!dataBreakpointId); + break; + case 'readWrite': + this.breakWhenValueIsAccessedSupported.set(!!dataBreakpointId); + break; + } + }); + } } const context: IVariablesContext = { @@ -435,7 +459,29 @@ CommandsRegistry.registerCommand({ handler: async (accessor: ServicesAccessor) => { const debugService = accessor.get(IDebugService); if (dataBreakpointInfoResponse) { - await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes); + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'write'); + } + } +}); + +export const BREAK_WHEN_VALUE_IS_ACCESSED_ID = 'debug.breakWhenValueIsAccessed'; +CommandsRegistry.registerCommand({ + id: BREAK_WHEN_VALUE_IS_ACCESSED_ID, + handler: async (accessor: ServicesAccessor) => { + const debugService = accessor.get(IDebugService); + if (dataBreakpointInfoResponse) { + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'readWrite'); + } + } +}); + +export const BREAK_WHEN_VALUE_IS_READ_ID = 'debug.breakWhenValueIsRead'; +CommandsRegistry.registerCommand({ + id: BREAK_WHEN_VALUE_IS_READ_ID, + handler: async (accessor: ServicesAccessor) => { + const debugService = accessor.get(IDebugService); + if (dataBreakpointInfoResponse) { + await debugService.addDataBreakpoint(dataBreakpointInfoResponse.description, dataBreakpointInfoResponse.dataId!, !!dataBreakpointInfoResponse.canPersist, dataBreakpointInfoResponse.accessTypes, 'read'); } } }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 632cb17c069..da4772034b0 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -58,6 +58,7 @@ export const CONTEXT_CALLSTACK_ITEM_STOPPED = new RawContextKey('callSt export const CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD = new RawContextKey('callStackSessionHasOneThread', false, { type: 'boolean', description: nls.localize('callStackSessionHasOneThread', "True when the focused session in the CALL STACK view has exactly one thread. Used internally for inline menus in the CALL STACK view.") }); export const CONTEXT_WATCH_ITEM_TYPE = new RawContextKey('watchItemType', undefined, { type: 'string', description: nls.localize('watchItemType', "Represents the item type of the focused element in the WATCH view. For example: 'expression', 'variable'") }); export const CONTEXT_BREAKPOINT_ITEM_TYPE = new RawContextKey('breakpointItemType', undefined, { type: 'string', description: nls.localize('breakpointItemType', "Represents the item type of the focused element in the BREAKPOINTS view. For example: 'breakpoint', 'exceptionBreakppint', 'functionBreakpoint', 'dataBreakpoint'") }); +export const CONTEXT_BREAKPOINT_ACCESS_TYPE = new RawContextKey('breakpointAccessType', undefined, { type: 'string', description: nls.localize('breakpointAccessType', "Represents the access type of the focused data breakpoint in the BREAKPOINTS view. For example: 'read', 'readWrite', 'write'") }); export const CONTEXT_BREAKPOINT_SUPPORTS_CONDITION = new RawContextKey('breakpointSupportsCondition', false, { type: 'boolean', description: nls.localize('breakpointSupportsCondition', "True when the focused breakpoint supports conditions.") }); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false, { type: 'boolean', description: nls.localize('loadedScriptsSupported', "True when the focused sessions supports the LOADED SCRIPTS view") }); export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined, { type: 'string', description: nls.localize('loadedScriptsItemType', "Represents the item type of the focused element in the LOADED SCRIPTS view.") }); @@ -72,6 +73,8 @@ export const CONTEXT_DEBUGGERS_AVAILABLE = new RawContextKey('debuggers export const CONTEXT_DEBUG_PROTOCOL_VARIABLE_MENU_CONTEXT = new RawContextKey('debugProtocolVariableMenuContext', undefined, { type: 'string', description: nls.localize('debugProtocolVariableMenuContext', "Represents the context the debug adapter sets on the focused variable in the VARIABLES view.") }); export const CONTEXT_SET_VARIABLE_SUPPORTED = new RawContextKey('debugSetVariableSupported', false, { type: 'boolean', description: nls.localize('debugSetVariableSupported', "True when the focused session supports 'setVariable' request.") }); export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey('breakWhenValueChangesSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueChangesSupported', "True when the focused session supports to break when value changes.") }); +export const CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED = new RawContextKey('breakWhenValueIsAccessedSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsAccessedSupported', "True when the focused breakpoint supports to break when value is accessed.") }); +export const CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED = new RawContextKey('breakWhenValueIsReadSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsReadSupported', "True when the focused breakpoint supports to break when value is read.") }); export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey('variableEvaluateNamePresent', false, { type: 'boolean', description: nls.localize('variableEvaluateNamePresent', "True when the focused variable has an 'evalauteName' field set") }); export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") }); export const CONTEXT_MULTI_SESSION_REPL = new RawContextKey('multiSessionRepl', false, { type: 'boolean', description: nls.localize('multiSessionRepl', "True when there is more than 1 debug console.") }); @@ -423,6 +426,7 @@ export interface IDataBreakpoint extends IBaseBreakpoint { readonly description: string; readonly dataId: string; readonly canPersist: boolean; + readonly accessType: DebugProtocol.DataBreakpointAccessType; } export interface IExceptionInfo { @@ -871,7 +875,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise; + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): Promise; /** * Removes all data breakpoints. If id is passed only removes the data breakpoint with the passed id. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4da1f90e7cc..a72057fba60 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -121,13 +121,20 @@ export class ExpressionContainer implements IExpressionContainer { private async fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise { try { const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count); - return response && response.body && response.body.variables - ? distinct(response.body.variables.filter(v => !!v), v => v.name).map((v: IDebugProtocolVariableWithContext) => { - if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') { - return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type, v.__vscodeVariableMenuContext); - } - return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, { kind: 'virtual' }, undefined, undefined, false); - }) : []; + if (!response || !response.body || !response.body.variables) { + return []; + } + + const nameCount = new Map(); + return response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => { + if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') { + const count = nameCount.get(v.name) || 0; + const idDuplicationIndex = count > 0 ? count.toString() : ''; + nameCount.set(v.name, count + 1); + return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type, v.__vscodeVariableMenuContext, true, 0, idDuplicationIndex); + } + return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, { kind: 'virtual' }, undefined, undefined, false); + }); } catch (e) { return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, { kind: 'virtual' }, undefined, undefined, false)]; } @@ -225,9 +232,10 @@ export class Variable extends ExpressionContainer implements IExpression { public type: string | undefined = undefined, public variableMenuContext: string | undefined = undefined, public available = true, - startOfVariables = 0 + startOfVariables = 0, + idDuplicationIndex = '', ) { - super(session, threadId, reference, `variable:${parent.getId()}:${name}`, namedVariables, indexedVariables, startOfVariables); + super(session, threadId, reference, `variable:${parent.getId()}:${name}:${idDuplicationIndex}`, namedVariables, indexedVariables, startOfVariables); this.value = value || ''; } @@ -836,7 +844,8 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { hitCondition: string | undefined, condition: string | undefined, logMessage: string | undefined, - private accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, + public accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, + public accessType: DebugProtocol.DataBreakpointAccessType, id = generateUuid() ) { super(enabled, hitCondition, condition, logMessage, id); @@ -847,7 +856,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { result.description = this.description; result.dataId = this.dataId; result.accessTypes = this.accessTypes; - + result.accessType = this.accessType; return result; } @@ -1284,8 +1293,8 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed, sessionOnly: false }); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): void { - const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes); + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, accessType: DebugProtocol.DataBreakpointAccessType): void { + const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes, accessType); this.dataBreakopints.push(newDataBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint], sessionOnly: false }); } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index 8d2c5e12bb9..f237fff5ad1 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -69,7 +69,7 @@ export class DebugStorage { let result: DataBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes); + return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes, dbp.accessType); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index f122602c397..d98cb1cba8e 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -228,13 +228,15 @@ suite('Debug - Breakpoints', () => { let eventCount = 0; model.onDidChangeBreakpoints(() => eventCount++); - model.addDataBreakpoint('label', 'id', true, ['read']); - model.addDataBreakpoint('second', 'secondId', false, ['readWrite']); + model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); + model.addDataBreakpoint('second', 'secondId', false, ['readWrite'], 'readWrite'); const dataBreakpoints = model.getDataBreakpoints(); assert.strictEqual(dataBreakpoints[0].canPersist, true); assert.strictEqual(dataBreakpoints[0].dataId, 'id'); + assert.strictEqual(dataBreakpoints[0].accessType, 'read'); assert.strictEqual(dataBreakpoints[1].canPersist, false); assert.strictEqual(dataBreakpoints[1].description, 'second'); + assert.strictEqual(dataBreakpoints[1].accessType, 'readWrite'); assert.strictEqual(eventCount, 2); @@ -282,7 +284,7 @@ suite('Debug - Breakpoints', () => { assert.strictEqual(result.message, 'Disabled Logpoint'); assert.strictEqual(result.icon.id, 'debug-breakpoint-log-disabled'); - model.addDataBreakpoint('label', 'id', true, ['read']); + model.addDataBreakpoint('label', 'id', true, ['read'], 'read'); const dataBreakpoints = model.getDataBreakpoints(); result = getBreakpointMessageAndIcon(State.Stopped, true, dataBreakpoints[0]); assert.strictEqual(result.message, 'Data Breakpoint'); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 60e1ca4c07b..5f7ef00427f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -67,6 +67,7 @@ import { registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { insane } from 'vs/base/common/insane/insane'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; function removeEmbeddedSVGs(documentContent: string): string { return insane(documentContent, { @@ -1000,7 +1001,7 @@ export class ExtensionEditor extends EditorPane { const scrollableContent = new DomScrollableElement(content, { useShadows: false }); append(parent, scrollableContent.getDomNode()); - const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content); + const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content, new Delegate()); const extensions: IExtension[] = await getExtensions(manifest.extensionPack!, this.extensionsWorkbenchService); extensionsGridView.setExtensions(extensions); scrollableContent.scanDomNode(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 5ef55d54b7a..953846bf84f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -65,7 +65,7 @@ import { EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { isArray } from 'vs/base/common/types'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { Promises } from 'vs/base/common/async'; @@ -332,7 +332,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @INotificationService private readonly notificationService: INotificationService, + @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, ) { super(); @@ -466,28 +466,11 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: async () => { await this.extensionsWorkbenchService.checkForUpdates(); const outdated = this.extensionsWorkbenchService.outdated; - if (!outdated.length) { - this.notificationService.info(localize('noUpdatesAvailable', "All extensions are up to date.")); - return; + if (outdated.length) { + return runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@outdated ')); + } else { + return this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date."), []); } - - let msgAvailableExtensions = outdated.length === 1 ? localize('singleUpdateAvailable', "An extension update is available.") : localize('updatesAvailable', "{0} extension updates are available.", outdated.length); - - const disabledExtensionsCount = outdated.filter(ext => ext.local && !this.extensionEnablementService.isEnabled(ext.local)).length; - if (disabledExtensionsCount) { - if (outdated.length === 1) { - msgAvailableExtensions = localize('singleDisabledUpdateAvailable', "An update to an extension which is disabled is available."); - } else if (disabledExtensionsCount === 1) { - msgAvailableExtensions = localize('updatesAvailableOneDisabled', "{0} extension updates are available. One of them is for a disabled extension.", outdated.length); - } else if (disabledExtensionsCount === outdated.length) { - msgAvailableExtensions = localize('updatesAvailableAllDisabled', "{0} extension updates are available. All of them are for disabled extensions.", outdated.length); - } else { - msgAvailableExtensions = localize('updatesAvailableIncludingDisabled', "{0} extension updates are available. {1} of them are for disabled extensions.", outdated.length, disabledExtensionsCount); - } - } - - this.viewletService.openViewlet(VIEWLET_ID, true); - this.notificationService.info(msgAvailableExtensions); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 39750747195..1541755ba67 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -38,13 +38,14 @@ export class ExtensionsGridView extends Disposable { constructor( parent: HTMLElement, + delegate: Delegate, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); this.element = dom.append(parent, dom.$('.extensions-grid-view')); this.renderer = this.instantiationService.createInstance(Renderer, { onFocus: Event.None, onBlur: Event.None }); - this.delegate = new Delegate(); - this.disposableStore = new DisposableStore(); + this.delegate = delegate; + this.disposableStore = this._register(new DisposableStore()); } setExtensions(extensions: IExtension[]): void { @@ -55,7 +56,6 @@ export class ExtensionsGridView extends Disposable { private renderExtension(extension: IExtension, index: number): void { const extensionContainer = dom.append(this.element, dom.$('.extension-container')); extensionContainer.style.height = `${this.delegate.getHeight()}px`; - extensionContainer.style.width = `275px`; extensionContainer.setAttribute('tabindex', '0'); const template = this.renderer.renderTemplate(extensionContainer); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index de49c6cc16a..5a8be901f33 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -368,10 +368,6 @@ line-height: 22px; } -.extension-editor > .body > .content details > summary::-webkit-details-marker { - color: rgba(128, 128, 128, 0.5); -} - .extension-editor > .body > .content table tr:nth-child(odd) { background-color: rgba(130, 130, 130, 0.04); } @@ -499,6 +495,7 @@ } .extension-editor .extensions-grid-view > .extension-container { + height: 275px; margin: 0 10px 20px 0; } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 4980ee077d5..1ef4caee830 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -9,14 +9,13 @@ import { basename, isEqual } from 'vs/base/common/resources'; import { Action, IAction } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, ITextFileSaveAsOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceMap } from 'vs/base/common/map'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; 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'; @@ -54,7 +53,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa @IEditorService private readonly editorService: IEditorService, @ITextModelService textModelService: ITextModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IStorageService private readonly storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService ) { super(); @@ -78,7 +77,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa let activeConflictResolutionResource: URI | undefined; const activeInput = this.editorService.activeEditor; - if (activeInput instanceof DiffEditorInput && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { + if (activeInput instanceof DiffEditorInput) { const resource = activeInput.originalInput.resource; if (resource?.scheme === CONFLICT_RESOLUTION_SCHEME) { isActiveEditorSaveConflictResolution = true; @@ -331,16 +330,26 @@ class SaveModelAsAction extends Action { } private findEditor(): IEditorIdentifier | undefined { - for (const group of this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + let preferredMatchingEditor: IEditorIdentifier | undefined; + let otherMatchingEditors: IEditorIdentifier[] = []; + + FindEditorLoop: 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 }; + // We prefer a `FileEditorInput` for "Save As", but it is possible + // that a custom editor is leveraging the text file model and as + // such we need to fallback to any other editor having the resource + // opened for running the save. + preferredMatchingEditor = { editor, groupId: group.id }; + break FindEditorLoop; } + + otherMatchingEditors.push({ editor, groupId: group.id }); } } - return undefined; + return preferredMatchingEditor || otherMatchingEditors[0]; } } @@ -387,9 +396,16 @@ class ConfigureSaveConflictAction extends Action { } } -export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, resource: URI) => { +export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { + return acceptOrRevertLocalChangesCommand(accessor, resource, true); +}; + +export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { + return acceptOrRevertLocalChangesCommand(accessor, resource, false); +}; + +async function acceptOrRevertLocalChangesCommand(accessor: ServicesAccessor, resource: URI, accept: boolean) { const editorService = accessor.get(IEditorService); - const resolverService = accessor.get(ITextModelService); const editorPane = editorService.activeEditorPane; if (!editorPane) { @@ -399,58 +415,20 @@ export const acceptLocalChangesCommand = async (accessor: ServicesAccessor, reso const editor = editorPane.input; const group = editorPane.group; - const reference = await resolverService.createModelReference(resource); - const model = reference.object as IResolvedTextFileEditorModel; + // Hide any previously shown message about how to use these actions + clearPendingResolveSaveConflictMessages(); - try { - - // hide any previously shown message about how to use these actions - clearPendingResolveSaveConflictMessages(); - - // Trigger save - await model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); - - // Reopen file input - await editorService.openEditor({ resource: model.resource }, group); - - // Clean up - group.closeEditor(editor); - editor.dispose(); - } finally { - reference.dispose(); - } -}; - -export const revertLocalChangesCommand = async (accessor: ServicesAccessor, resource: URI) => { - const editorService = accessor.get(IEditorService); - const resolverService = accessor.get(ITextModelService); - - const editorPane = editorService.activeEditorPane; - if (!editorPane) { - return; + // Accept or revert + if (accept) { + const options: ITextFileSaveAsOptions = { ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }; + await editorService.save({ editor, groupId: group.id }, options); + } else { + await editorService.revert({ editor, groupId: group.id }); } - const editor = editorPane.input; - const group = editorPane.group; + // Reopen original editor + await editorService.openEditor({ resource }, group); - const reference = await resolverService.createModelReference(resource); - const model = reference.object as ITextFileEditorModel; - - try { - - // hide any previously shown message about how to use these actions - clearPendingResolveSaveConflictMessages(); - - // Revert on model - await model.revert(); - - // Reopen file input - await editorService.openEditor({ resource: model.resource }, group); - - // Clean up - group.closeEditor(editor); - editor.dispose(); - } finally { - reference.dispose(); - } -}; + // Clean up + return group.closeEditor(editor); +} diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index d4b88a84259..4809b811ad6 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -170,6 +170,10 @@ export class ExplorerItem { return this.resource.toString(); } + toString(): string { + return `ExplorerItem: ${this.name}`; + } + get isRoot(): boolean { return this === this.root; } diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index 0d70177238a..6a8d6e8e761 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -38,6 +38,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { static readonly configName = 'editor.defaultFormatter'; static extensionIds: (string | null)[] = []; + static extensionItemLabels: string[] = []; static extensionDescriptions: string[] = []; constructor( @@ -73,15 +74,18 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { }); DefaultFormatter.extensionIds.length = 0; + DefaultFormatter.extensionItemLabels.length = 0; DefaultFormatter.extensionDescriptions.length = 0; DefaultFormatter.extensionIds.push(null); + DefaultFormatter.extensionItemLabels.push(nls.localize('null', 'None')); DefaultFormatter.extensionDescriptions.push(nls.localize('nullFormatterDescription', "None")); for (const extension of extensions) { if (extension.main || extension.browser) { DefaultFormatter.extensionIds.push(extension.identifier.value); - DefaultFormatter.extensionDescriptions.push(extension.description || ''); + DefaultFormatter.extensionItemLabels.push(extension.displayName ?? ''); + DefaultFormatter.extensionDescriptions.push(extension.description ?? ''); } } } @@ -182,6 +186,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: ['string', 'null'], default: null, enum: DefaultFormatter.extensionIds, + enumItemLabels: DefaultFormatter.extensionItemLabels, markdownEnumDescriptions: DefaultFormatter.extensionDescriptions } } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index c092329a8c0..f66d8357d67 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -695,7 +695,7 @@ export class MarkerViewModel extends Disposable { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { - type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } + type: CodeActionTriggerType.Invoke, filter: { include: CodeActionKind.QuickFix } }, Progress.None, cancellationToken).then(actions => { return this._register(actions); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts new file mode 100644 index 00000000000..3febe508c21 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations.ts @@ -0,0 +1,521 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { localize } from 'vs/nls'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { Range } from 'vs/editor/common/core/range'; +import { CellOverflowToolbarGroups, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, NotebookCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { CellEditState, expandCellRangesWithHiddenCells, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { CellEditType, CellKind, cellRangeContains, cellRangesToIndexes, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { cloneNotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { IBulkEditService, ResourceEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; + +const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp'; +const MOVE_CELL_DOWN_COMMAND_ID = 'notebook.cell.moveDown'; +const COPY_CELL_UP_COMMAND_ID = 'notebook.cell.copyUp'; +const COPY_CELL_DOWN_COMMAND_ID = 'notebook.cell.copyDown'; +const SPLIT_CELL_COMMAND_ID = 'notebook.cell.split'; +const JOIN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.joinAbove'; +const JOIN_CELL_BELOW_COMMAND_ID = 'notebook.cell.joinBelow'; + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: MOVE_CELL_UP_COMMAND_ID, + title: localize('notebookActions.moveCellUp', "Move Cell Up"), + icon: icons.moveUpIcon, + keybinding: { + primary: KeyMod.Alt | KeyCode.UpArrow, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + return moveCellRange(context, 'up'); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: MOVE_CELL_DOWN_COMMAND_ID, + title: localize('notebookActions.moveCellDown', "Move Cell Down"), + icon: icons.moveDownIcon, + keybinding: { + primary: KeyMod.Alt | KeyCode.DownArrow, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + return moveCellRange(context, 'down'); + } +}); + +export async function moveCellRange(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { + const viewModel = context.notebookEditor.viewModel; + if (!viewModel) { + return; + } + + if (!viewModel.metadata.editable) { + return; + } + + const selections = context.notebookEditor.getSelections(); + const modelRanges = expandCellRangesWithHiddenCells(context.notebookEditor, context.notebookEditor.viewModel!, selections); + const range = modelRanges[0]; + if (!range || range.start === range.end) { + return; + } + + if (direction === 'up') { + if (range.start === 0) { + return; + } + + const indexAbove = range.start - 1; + const finalSelection = { start: range.start - 1, end: range.end - 1 }; + const focus = context.notebookEditor.getFocus(); + const newFocus = cellRangeContains(range, focus) ? { start: focus.start - 1, end: focus.end - 1 } : { start: range.start - 1, end: range.start }; + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Move, + index: indexAbove, + length: 1, + newIdx: range.end - 1 + }], + true, + { + kind: SelectionStateType.Index, + focus: viewModel.getFocus(), + selections: viewModel.getSelections() + }, + () => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }), + undefined + ); + const focusRange = viewModel.getSelections()[0] ?? viewModel.getFocus(); + context.notebookEditor.revealCellRangeInView(focusRange); + } else { + if (range.end >= viewModel.length) { + return; + } + + const indexBelow = range.end; + const finalSelection = { start: range.start + 1, end: range.end + 1 }; + const focus = context.notebookEditor.getFocus(); + const newFocus = cellRangeContains(range, focus) ? { start: focus.start + 1, end: focus.end + 1 } : { start: range.start + 1, end: range.start + 2 }; + + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Move, + index: indexBelow, + length: 1, + newIdx: range.start + }], + true, + { + kind: SelectionStateType.Index, + focus: viewModel.getFocus(), + selections: viewModel.getSelections() + }, + () => ({ kind: SelectionStateType.Index, focus: newFocus, selections: [finalSelection] }), + undefined + ); + + const focusRange = viewModel.getSelections()[0] ?? viewModel.getFocus(); + context.notebookEditor.revealCellRangeInView(focusRange); + } +} + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: COPY_CELL_UP_COMMAND_ID, + title: localize('notebookActions.copyCellUp', "Copy Cell Up"), + keybinding: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + return copyCellRange(context, 'up'); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: COPY_CELL_DOWN_COMMAND_ID, + title: localize('notebookActions.copyCellDown', "Copy Cell Down"), + keybinding: { + primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + return copyCellRange(context, 'down'); + } +}); + +export async function copyCellRange(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { + const viewModel = context.notebookEditor.viewModel; + if (!viewModel) { + return; + } + + if (!viewModel.metadata.editable) { + return; + } + + const selections = context.notebookEditor.getSelections(); + const modelRanges = expandCellRangesWithHiddenCells(context.notebookEditor, context.notebookEditor.viewModel!, selections); + const range = modelRanges[0]; + if (!range || range.start === range.end) { + return; + } + + if (direction === 'up') { + // insert up, without changing focus and selections + const focus = viewModel.getFocus(); + const selections = viewModel.getSelections(); + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Replace, + index: range.end, + count: 0, + cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)) + }], + true, + { + kind: SelectionStateType.Index, + focus: focus, + selections: selections + }, + () => ({ kind: SelectionStateType.Index, focus: focus, selections: selections }), + undefined + ); + } else { + // insert down, move selections + const focus = viewModel.getFocus(); + const selections = viewModel.getSelections(); + const newCells = cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)); + const countDelta = newCells.length; + const newFocus = { start: focus.start + countDelta, end: focus.end + countDelta }; + const newSelections = [{ start: range.start + countDelta, end: range.end + countDelta }]; + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Replace, + index: range.end, + count: 0, + cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)) + }], + true, + { + kind: SelectionStateType.Index, + focus: focus, + selections: selections + }, + () => ({ kind: SelectionStateType.Index, focus: newFocus, selections: newSelections }), + undefined + ); + + const focusRange = viewModel.getSelections()[0] ?? viewModel.getFocus(); + context.notebookEditor.revealCellRangeInView(focusRange); + } +} + +export async function splitCell(context: INotebookCellActionContext): Promise { + const newCells = await context.notebookEditor.splitNotebookCell(context.cell); + if (newCells) { + context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], 'editor'); + } +} + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: SPLIT_CELL_COMMAND_ID, + title: localize('notebookActions.splitCell', "Split Cell"), + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), + order: CellToolbarOrder.SplitCell, + group: CELL_TITLE_CELL_GROUP_ID, + // alt: { + // id: JOIN_CELL_BELOW_COMMAND_ID, + // title: localize('notebookActions.joinCellBelow', "Join with Next Cell") + // } + }, + icon: icons.splitCellIcon, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH), + weight: KeybindingWeight.WorkbenchContrib + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + return splitCell(context); + } +}); + +export async function joinNotebookCells(viewModel: NotebookViewModel, range: ICellRange, direction: 'above' | 'below', constraint?: CellKind): Promise<{ edits: ResourceEdit[], cell: ICellViewModel, endFocus: ICellRange, endSelections: ICellRange[] } | null> { + if (!viewModel || !viewModel.metadata.editable) { + return null; + } + + const cells = viewModel.viewCells.slice(range.start, range.end); + + if (!cells.length) { + return null; + } + + if (range.start === 0 && direction === 'above') { + return null; + } + + if (range.end === viewModel.length && direction === 'below') { + return null; + } + + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + if (!cell.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) { + return null; + } + + if (constraint && cell.cellKind !== constraint) { + return null; + } + } + + if (direction === 'above') { + const above = viewModel.viewCells[range.start - 1] as CellViewModel; + if (constraint && above.cellKind !== constraint) { + return null; + } + + if (!above.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) { + return null; + } + + const insertContent = cells.map(cell => (cell.textBuffer.getEOL() ?? '') + cell.getText()).join(''); + const aboveCellLineCount = above.textBuffer.getLineCount(); + const aboveCellLastLineEndColumn = above.textBuffer.getLineLength(aboveCellLineCount); + + return { + edits: [ + new ResourceTextEdit(above.uri, { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }), + new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: range.start, + count: range.end - range.start, + cells: [] + } + ) + ], + cell: above, + endFocus: { start: range.start - 1, end: range.start }, + endSelections: [{ start: range.start - 1, end: range.start }] + }; + } else { + const below = viewModel.viewCells[range.end] as CellViewModel; + if (constraint && below.cellKind !== constraint) { + return null; + } + + if (!below.getEvaluatedMetadata(viewModel.notebookDocument.metadata).editable) { + return null; + } + + const cell = cells[0]; + const restCells = [...cells.slice(1), below]; + const insertContent = restCells.map(cl => (cl.textBuffer.getEOL() ?? '') + cl.getText()).join(''); + + const cellLineCount = cell.textBuffer.getLineCount(); + const cellLastLineEndColumn = cell.textBuffer.getLineLength(cellLineCount); + + return { + edits: [ + new ResourceTextEdit(cell.uri, { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }), + new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: range.start + 1, + count: range.end - range.start, + cells: [] + } + ) + ], + cell, + endFocus: { start: range.start, end: range.start + 1 }, + endSelections: [{ start: range.start, end: range.start + 1 }] + }; + } +} + +export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { + const viewModel = context.notebookEditor.viewModel; + let ret: { + edits: ResourceEdit[]; + cell: ICellViewModel; + endFocus: ICellRange; + endSelections: ICellRange[]; + } | null = null; + + if (context.ui) { + const cellIndex = viewModel.getCellIndex(context.cell); + ret = await joinNotebookCells(viewModel, { start: cellIndex, end: cellIndex + 1 }, direction); + if (!ret) { + return; + } + + await bulkEditService.apply( + ret?.edits, + { quotableLabel: 'Join Notebook Cells' } + ); + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: ret.endFocus, selections: ret.endSelections }); + ret.cell.editState = CellEditState.Editing; + context.notebookEditor.revealCellRangeInView(viewModel.getFocus()); + } else { + const selections = viewModel.getSelections(); + if (!selections.length) { + return; + } + + const focus = viewModel.getFocus(); + let edits: ResourceEdit[] = []; + let cell: ICellViewModel | null = null; + let cells: ICellViewModel[] = []; + + for (let i = selections.length - 1; i >= 0; i--) { + const selection = selections[i]; + const containFocus = cellRangeContains(selection, focus); + + if ( + selection.end >= viewModel.length && direction === 'below' + || selection.start === 0 && direction === 'above' + ) { + if (containFocus) { + cell = viewModel.getCellByIndex(focus.start); + } + + cells.push(...viewModel.viewCells.slice(selection.start, selection.end)); + continue; + } + + const singleRet = await joinNotebookCells(viewModel, selection, direction); + + if (!singleRet) { + return; + } + + edits.push(...singleRet.edits); + cells.push(singleRet.cell); + + if (containFocus) { + cell = singleRet.cell; + } + } + + if (!edits.length) { + return; + } + + if (!cell || !cells.length) { + return; + } + + await bulkEditService.apply( + edits, + { quotableLabel: 'Join Notebook Cells' } + ); + + cells.forEach(cell => { + cell.editState = CellEditState.Editing; + }); + + viewModel.updateSelectionsState({ kind: SelectionStateType.Handle, primary: cell.handle, selections: cells.map(cell => cell.handle) }); + context.notebookEditor.revealCellRangeInView(viewModel.getFocus()); + } +} + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: JOIN_CELL_ABOVE_COMMAND_ID, + title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), + keybinding: { + when: NOTEBOOK_EDITOR_FOCUSED, + primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_J, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: CellOverflowToolbarGroups.Edit, + order: 10 + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const bulkEditService = accessor.get(IBulkEditService); + return joinCellsWithSurrounds(bulkEditService, context, 'above'); + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: JOIN_CELL_BELOW_COMMAND_ID, + title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), + keybinding: { + when: NOTEBOOK_EDITOR_FOCUSED, + primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J, + weight: KeybindingWeight.WorkbenchContrib + }, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: CellOverflowToolbarGroups.Edit, + order: 11 + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const bulkEditService = accessor.get(IBulkEditService); + return joinCellsWithSurrounds(bulkEditService, context, 'below'); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/test/cellOperations.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/test/cellOperations.test.ts new file mode 100644 index 00000000000..9a28c1d777b --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellOperations/test/cellOperations.test.ts @@ -0,0 +1,256 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { Range } from 'vs/editor/common/core/range'; +import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; +import { copyCellRange, joinNotebookCells, moveCellRange } from 'vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations'; +import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; +import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; + +suite('CellOperations', () => { + test('Move cells - single cell', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] }); + await moveCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells[2].getText(), 'var b = 1;'); + }); + }); + + test('Move cells - multiple cells in a selection', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 0, end: 2 }] }); + await moveCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells[0].getText(), '# header b'); + assert.strictEqual(viewModel.viewCells[1].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[2].getText(), 'var b = 1;'); + }); + }); + + test('Move cells - move with folding ranges', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 1, true); + viewModel.updateFoldingRanges(foldingModel.regions); + editor.setHiddenAreas([{ start: 1, end: 2 }]); + editor.setHiddenAreas(viewModel.getHiddenRanges()); + + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); + await moveCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells[0].getText(), '# header b'); + assert.strictEqual(viewModel.viewCells[1].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[2].getText(), 'var b = 1;'); + }); + }); + + test('Copy/duplicate cells - single cell', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] }); + await copyCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells.length, 6); + assert.strictEqual(viewModel.viewCells[1].getText(), 'var b = 1;'); + assert.strictEqual(viewModel.viewCells[2].getText(), 'var b = 1;'); + }); + }); + + test('Copy/duplicate cells - multiple cells in a selection', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 0, end: 2 }] }); + await copyCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells.length, 7); + assert.strictEqual(viewModel.viewCells[0].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[1].getText(), 'var b = 1;'); + assert.strictEqual(viewModel.viewCells[2].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[3].getText(), 'var b = 1;'); + }); + }); + + test('Copy/duplicate cells - move with folding ranges', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 1, true); + viewModel.updateFoldingRanges(foldingModel.regions); + editor.setHiddenAreas([{ start: 1, end: 2 }]); + editor.setHiddenAreas(viewModel.getHiddenRanges()); + + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); + await copyCellRange({ notebookEditor: editor, cell: viewModel.viewCells[1] }, 'down'); + assert.strictEqual(viewModel.viewCells.length, 7); + assert.strictEqual(viewModel.viewCells[0].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[1].getText(), 'var b = 1;'); + assert.strictEqual(viewModel.viewCells[2].getText(), '# header a'); + assert.strictEqual(viewModel.viewCells[3].getText(), 'var b = 1;'); + }); + }); + + test('Join cell with below - single cell', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 3, end: 4 }, selections: [{ start: 3, end: 4 }] }); + const ret = await joinNotebookCells(editor.viewModel, { start: 3, end: 4 }, 'below'); + assert.strictEqual(ret?.edits.length, 2); + assert.deepStrictEqual(ret?.edits[0], new ResourceTextEdit(viewModel.viewCells[3].uri, { + range: new Range(1, 11, 1, 11), text: viewModel.viewCells[4].textBuffer.getEOL() + 'var c = 3;' + })); + assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: 4, + count: 1, + cells: [] + } + )); + }); + }); + + test('Join cell with above - single cell', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 3, end: 4 }, selections: [{ start: 3, end: 4 }] }); + const ret = await joinNotebookCells(editor.viewModel, { start: 4, end: 5 }, 'above'); + assert.strictEqual(ret?.edits.length, 2); + assert.deepStrictEqual(ret?.edits[0], new ResourceTextEdit(viewModel.viewCells[3].uri, { + range: new Range(1, 11, 1, 11), text: viewModel.viewCells[4].textBuffer.getEOL() + 'var c = 3;' + })); + assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: 4, + count: 1, + cells: [] + } + )); + }); + }); + + test('Join cell with below - multiple cells', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 0, end: 2 }] }); + const ret = await joinNotebookCells(editor.viewModel, { start: 0, end: 2 }, 'below'); + assert.strictEqual(ret?.edits.length, 2); + assert.deepStrictEqual(ret?.edits[0], new ResourceTextEdit(viewModel.viewCells[0].uri, { + range: new Range(1, 11, 1, 11), text: viewModel.viewCells[1].textBuffer.getEOL() + 'var b = 2;' + viewModel.viewCells[2].textBuffer.getEOL() + 'var c = 3;' + })); + assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: 1, + count: 2, + cells: [] + } + )); + }); + }); + + test('Join cell with above - multiple cells', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + async (editor, accessor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 2, end: 3 }, selections: [{ start: 1, end: 3 }] }); + const ret = await joinNotebookCells(editor.viewModel, { start: 1, end: 3 }, 'above'); + assert.strictEqual(ret?.edits.length, 2); + assert.deepStrictEqual(ret?.edits[0], new ResourceTextEdit(viewModel.viewCells[0].uri, { + range: new Range(1, 11, 1, 11), text: viewModel.viewCells[1].textBuffer.getEOL() + 'var b = 2;' + viewModel.viewCells[2].textBuffer.getEOL() + 'var c = 3;' + })); + assert.deepStrictEqual(ret?.edits[1], new ResourceNotebookCellEdit(viewModel.notebookDocument.uri, + { + editType: CellEditType.Replace, + index: 1, + count: 2, + cells: [] + } + )); + }); + }); +}); + diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 0d759a8d57d..03db8421ef2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -3,19 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getNotebookEditorFromEditorPane, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import * as UUID from 'vs/base/common/uuid'; +import { expandCellRangesWithHiddenCells, getNotebookEditorFromEditorPane, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellEditType, ICellEditOperation, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { cloneNotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { CellEditType, ICellEditOperation, ICellRange, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import * as platform from 'vs/base/common/platform'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CellOverflowToolbarGroups, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; class NotebookClipboardContribution extends Disposable { @@ -46,6 +54,10 @@ class NotebookClipboardContribution extends Disposable { return false; } + if (!editor.hasModel()) { + return false; + } + if (editor.hasOutputTextSelection()) { document.execCommand('copy'); return true; @@ -53,7 +65,8 @@ class NotebookClipboardContribution extends Disposable { const clipboardService = accessor.get(IClipboardService); const notebookService = accessor.get(INotebookService); - const selectedCells = editor.getSelectionViewModels(); + const selectionRanges = expandCellRangesWithHiddenCells(editor, editor.viewModel, editor.viewModel.getSelections()); + const selectedCells = this._cellRangeToViewCells(editor.viewModel, selectionRanges); if (!selectedCells.length) { return false; @@ -91,35 +104,11 @@ class NotebookClipboardContribution extends Disposable { return false; } - const cloneMetadata = (cell: NotebookCellTextModel) => { - return { - editable: cell.metadata?.editable, - 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 => ({ - outputs: output.outputs, - /* 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 => cloneCell(cell)).forEach(pasteCell => { + pasteCells.items.reverse().map(cell => cloneNotebookCellTextModel(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); }); @@ -133,7 +122,7 @@ class NotebookClipboardContribution extends Disposable { } let topPastedCell: CellViewModel | undefined = undefined; - pasteCells.items.reverse().map(cell => cloneCell(cell)).forEach(pasteCell => { + pasteCells.items.reverse().map(cell => cloneNotebookCellTextModel(cell)).forEach(pasteCell => { topPastedCell = viewModel.createCell(0, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); @@ -167,23 +156,30 @@ class NotebookClipboardContribution extends Disposable { const clipboardService = accessor.get(IClipboardService); const notebookService = accessor.get(INotebookService); - const selectedCells = editor.getSelectionViewModels(); + const selectionRanges = expandCellRangesWithHiddenCells(editor, viewModel, viewModel.getSelections()); + const selectedCells = this._cellRangeToViewCells(viewModel, selectionRanges); if (!selectedCells.length) { return false; } 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 firstSelectIndex = selectionIndexes.sort((a, b) => a[1] - b[1])[0][1]; + const edits: ICellEditOperation[] = selectionRanges.map(range => ({ editType: CellEditType.Replace, index: range.start, count: range.end - range.start, cells: [] })); + const firstSelectIndex = selectionRanges[0].start; + + /** + * If we have cells, 0, 1, 2, 3, 4, 5, 6 + * and cells 1, 2 are selected, and then we delete cells 1 and 2 + * the new focused cell should still be at index 1 + */ const newFocusedCellIndex = firstSelectIndex < viewModel.notebookDocument.cells.length ? firstSelectIndex : viewModel.notebookDocument.cells.length - 1; - viewModel.notebookDocument.applyEdits(edits, true, { kind: SelectionStateType.Index, selections: viewModel.getSelections() }, () => { + viewModel.notebookDocument.applyEdits(edits, true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: selectionRanges }, () => { return { kind: SelectionStateType.Index, + focus: { start: newFocusedCellIndex, end: newFocusedCellIndex + 1 }, selections: [{ start: newFocusedCellIndex, end: newFocusedCellIndex + 1 }] }; }, undefined, true); @@ -193,7 +189,254 @@ class NotebookClipboardContribution extends Disposable { }); } } + + private _cellRangeToViewCells(viewModel: NotebookViewModel, ranges: ICellRange[]) { + const cells: ICellViewModel[] = []; + ranges.forEach(range => { + cells.push(...viewModel.viewCells.slice(range.start, range.end)); + }); + + return cells; + } } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookClipboardContribution, LifecyclePhase.Ready); + +const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; +const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; +const PASTE_CELL_COMMAND_ID = 'notebook.cell.paste'; +const PASTE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.pasteAbove'; + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: COPY_CELL_COMMAND_ID, + title: localize('notebookActions.copy', "Copy Cell"), + menu: { + id: MenuId.NotebookCellTitle, + when: NOTEBOOK_EDITOR_FOCUSED, + group: CellOverflowToolbarGroups.Copy, + }, + keybinding: platform.isNative ? undefined : { + primary: KeyMod.CtrlCmd | KeyCode.KEY_C, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const clipboardService = accessor.get(IClipboardService); + const notebookService = accessor.get(INotebookService); + if (context.notebookEditor.hasOutputTextSelection()) { + document.execCommand('copy'); + return; + } + + const viewModel = context.notebookEditor.viewModel; + const selections = viewModel.getSelections(); + const targetCellIndex = viewModel.getCellIndex(context.cell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (containingSelection) { + const cells = viewModel.viewCells.slice(containingSelection.start, containingSelection.end); + + clipboardService.writeText(cells.map(cell => cell.getText()).join('\n')); + notebookService.setToCopy(cells.map(cell => cell.model), true); + } else { + clipboardService.writeText(context.cell.getText()); + notebookService.setToCopy([context.cell.model], true); + } + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: CUT_CELL_COMMAND_ID, + title: localize('notebookActions.cut', "Cut Cell"), + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + group: CellOverflowToolbarGroups.Copy, + }, + keybinding: platform.isNative ? undefined : { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.KEY_X, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const clipboardService = accessor.get(IClipboardService); + const notebookService = accessor.get(INotebookService); + clipboardService.writeText(context.cell.getText()); + const viewModel = context.notebookEditor.viewModel; + + if (!viewModel || !viewModel.metadata.editable) { + return; + } + + const selections = viewModel.getSelections(); + const targetCellIndex = viewModel.getCellIndex(context.cell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (containingSelection) { + const cellTextModels = viewModel.viewCells.slice(containingSelection.start, containingSelection.end).map(cell => cell.model); + let finalSelections: ICellRange[] = []; + const delta = containingSelection.end - containingSelection.start; + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; + + if (selection.end <= targetCellIndex) { + finalSelections.push(selection); + } else if (selection.start > targetCellIndex) { + finalSelections.push({ start: selection.start - delta, end: selection.end - delta }); + } else { + finalSelections.push({ start: containingSelection.start, end: containingSelection.start + 1 }); + } + } + + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: containingSelection.start, count: containingSelection.end - containingSelection.start, cells: [] + }], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => { + const newFocusCellIdx = containingSelection.start < context.notebookEditor.viewModel.notebookDocument.length ? containingSelection.start : context.notebookEditor.viewModel.notebookDocument.length - 1; + + return { + kind: SelectionStateType.Index, focus: { start: newFocusCellIdx, end: newFocusCellIdx + 1 }, selections: finalSelections + }; + }, undefined); + notebookService.setToCopy(cellTextModels, true); + } else { + viewModel.deleteCell(viewModel.getCellIndex(context.cell), true); + notebookService.setToCopy([context.cell.model], false); + } + } +}); + +registerAction2(class extends NotebookAction { + constructor() { + super( + { + id: PASTE_CELL_COMMAND_ID, + title: localize('notebookActions.paste', "Paste Cell"), + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), + group: CellOverflowToolbarGroups.Copy, + }, + keybinding: platform.isNative ? undefined : { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyCode.KEY_V, + win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, + linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { + const notebookService = accessor.get(INotebookService); + const pasteCells = notebookService.getToCopy(); + + const viewModel = context.notebookEditor.viewModel; + + if (!viewModel || !viewModel.metadata.editable) { + return; + } + + if (!pasteCells) { + return; + } + + const currCellIndex = context.cell && viewModel.getCellIndex(context.cell); + + let topPastedCell: CellViewModel | undefined = undefined; + pasteCells.items.reverse().map(cell => { + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; + }).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); + }); + + if (topPastedCell) { + context.notebookEditor.focusNotebookCell(topPastedCell, 'container'); + } + } +}); + +registerAction2(class extends NotebookCellAction { + constructor() { + super( + { + id: PASTE_CELL_ABOVE_COMMAND_ID, + title: localize('notebookActions.pasteAbove', "Paste Cell Above"), + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V, + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT + }, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const notebookService = accessor.get(INotebookService); + const pasteCells = notebookService.getToCopy(); + + const viewModel = context.notebookEditor.viewModel; + + if (!viewModel || !viewModel.metadata.editable) { + return; + } + + if (!pasteCells) { + return; + } + + const currCellIndex = viewModel.getCellIndex(context.cell); + + let topPastedCell: CellViewModel | undefined = undefined; + pasteCells.items.reverse().map(cell => { + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; + }).forEach(pasteCell => { + topPastedCell = viewModel.createCell(currCellIndex, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); + return; + }); + + if (topPastedCell) { + context.notebookEditor.focusNotebookCell(topPastedCell, 'container'); + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 3ee68e379b5..46371e2fe9e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -5,7 +5,6 @@ import * as glob from 'vs/base/common/glob'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as platform from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -13,7 +12,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { localize } from 'vs/nls'; import { Action2, IAction2Options, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; @@ -21,9 +19,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, SelectionStateType, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -59,18 +57,6 @@ const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit'; const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; -const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp'; -const MOVE_CELL_DOWN_COMMAND_ID = 'notebook.cell.moveDown'; -const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; -const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; -const PASTE_CELL_COMMAND_ID = 'notebook.cell.paste'; -const PASTE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.pasteAbove'; -const COPY_CELL_UP_COMMAND_ID = 'notebook.cell.copyUp'; -const COPY_CELL_DOWN_COMMAND_ID = 'notebook.cell.copyDown'; -const SPLIT_CELL_COMMAND_ID = 'notebook.cell.split'; -const JOIN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.joinAbove'; -const JOIN_CELL_BELOW_COMMAND_ID = 'notebook.cell.joinBelow'; - const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; @@ -90,16 +76,16 @@ export const NOTEBOOK_ACTIONS_CATEGORY = { value: localize('notebookActions.cate export const CELL_TITLE_CELL_GROUP_ID = 'inline/cell'; export const CELL_TITLE_OUTPUT_GROUP_ID = 'inline/output'; -const EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc +export const NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc -const enum CellToolbarOrder { +export const enum CellToolbarOrder { EditCell, SplitCell, SaveCell, ClearCellOutput } -const enum CellOverflowToolbarGroups { +export const enum CellOverflowToolbarGroups { Copy = '1_copy', Insert = '2_insert', Edit = '3_edit', @@ -172,7 +158,7 @@ function getContextFromUri(accessor: ServicesAccessor, context?: any) { return undefined; } -abstract class NotebookAction extends Action2 { +export abstract class NotebookAction extends Action2 { constructor(desc: IAction2Options) { if (desc.f1 !== false) { desc.f1 = false; @@ -223,7 +209,7 @@ abstract class NotebookAction extends Action2 { } } -abstract class NotebookCellAction extends NotebookAction { +export abstract class NotebookCellAction extends NotebookAction { protected isCellActionContext(context?: unknown): context is INotebookCellActionContext { return !!context && !!(context as INotebookCellActionContext).notebookEditor && !!(context as INotebookCellActionContext).cell; } @@ -272,7 +258,7 @@ registerAction2(class ExecuteCell extends NotebookCellAction { win: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter }, - weight: EDITOR_WIDGET_ACTION_WEIGHT + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }, menu: { id: MenuId.NotebookCellExecute, @@ -468,7 +454,7 @@ registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction { keybinding: { when: NOTEBOOK_CELL_LIST_FOCUSED, primary: KeyMod.Shift | KeyCode.Enter, - weight: EDITOR_WIDGET_ACTION_WEIGHT + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }, }); } @@ -505,7 +491,7 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction { keybinding: { when: NOTEBOOK_CELL_LIST_FOCUSED, primary: KeyMod.Alt | KeyCode.Enter, - weight: EDITOR_WIDGET_ACTION_WEIGHT + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }, }); } @@ -988,7 +974,7 @@ registerAction2(class extends NotebookCellAction { EditorContextKeys.hasMultipleSelections.toNegated() ), primary: KeyCode.Escape, - weight: EDITOR_WIDGET_ACTION_WEIGHT - 5 + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT - 5 }, }); } @@ -1025,303 +1011,73 @@ registerAction2(class extends NotebookCellAction { } async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const index = context.notebookEditor.viewModel.getCellIndex(context.cell); - const result = await context.notebookEditor.deleteNotebookCell(context.cell); + const viewModel = context.notebookEditor.viewModel; + if (!viewModel || !viewModel.metadata.editable) { + return; + } - if (result) { - // deletion succeeds, move focus to the next cell - const nextCellIdx = index < context.notebookEditor.viewModel.length ? index : context.notebookEditor.viewModel.length - 1; - if (nextCellIdx >= 0) { - context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel.viewCells[nextCellIdx], 'container'); + const selections = viewModel.getSelections(); + const targetCellIndex = viewModel.getCellIndex(context.cell); + const containingSelection = selections.find(selection => selection.start <= targetCellIndex && targetCellIndex < selection.end); + + if (containingSelection) { + let finalSelections: ICellRange[] = []; + const delta = containingSelection.end - containingSelection.start; + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; + + if (selection.end <= targetCellIndex) { + finalSelections.push(selection); + } else if (selection.start > targetCellIndex) { + finalSelections.push({ start: selection.start - delta, end: selection.end - delta }); + } else { + finalSelections.push({ start: containingSelection.start, end: containingSelection.start + 1 }); + } } - } - } -}); -async function moveCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { - const result = direction === 'up' ? - await context.notebookEditor.moveCellUp(context.cell) : - await context.notebookEditor.moveCellDown(context.cell); + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: containingSelection.start, count: containingSelection.end - containingSelection.start, cells: [] + }], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => { + const newFocusCellIdx = containingSelection.start < context.notebookEditor.viewModel.notebookDocument.length ? containingSelection.start : context.notebookEditor.viewModel.notebookDocument.length - 1; - if (result) { - // move cell command only works when the cell container has focus - context.notebookEditor.focusNotebookCell(result, 'container'); - } -} + return { + kind: SelectionStateType.Index, focus: { start: newFocusCellIdx, end: newFocusCellIdx + 1 }, selections: finalSelections + }; + }, undefined); + } else { + let finalSelections: ICellRange[] = []; + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; -async function copyCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { - const text = context.cell.getText(); - const newCellDirection = direction === 'up' ? 'above' : 'below'; - const newCell = context.notebookEditor.insertNotebookCell(context.cell, context.cell.cellKind, newCellDirection, text); - if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, 'container'); - } -} - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: MOVE_CELL_UP_COMMAND_ID, - title: localize('notebookActions.moveCellUp', "Move Cell Up"), - icon: icons.moveUpIcon, - keybinding: { - primary: KeyMod.Alt | KeyCode.UpArrow, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), - weight: KeybindingWeight.WorkbenchContrib + if (selection.end <= targetCellIndex) { + finalSelections.push(selection); + } else if (selection.start > targetCellIndex) { + finalSelections.push({ start: selection.start - 1, end: selection.end - 1 }); + } else { + finalSelections.push({ start: targetCellIndex, end: targetCellIndex + 1 }); } - }); - } + } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return moveCell(context, 'up'); - } -}); + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: targetCellIndex, count: 1, cells: [] + }], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => { + const newFocusCellIdx = targetCellIndex < context.notebookEditor.viewModel.notebookDocument.length ? targetCellIndex : context.notebookEditor.viewModel.notebookDocument.length - 1; + return { + kind: SelectionStateType.Index, focus: { start: newFocusCellIdx, end: newFocusCellIdx + 1 }, selections: finalSelections + }; + }, undefined); -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: MOVE_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.moveCellDown', "Move Cell Down"), - icon: icons.moveDownIcon, - keybinding: { - primary: KeyMod.Alt | KeyCode.DownArrow, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), - weight: KeybindingWeight.WorkbenchContrib - } - }); - } + // const result = await context.notebookEditor.deleteNotebookCell(context.cell); - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return moveCell(context, 'down'); - } -}); -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: COPY_CELL_COMMAND_ID, - title: localize('notebookActions.copy', "Copy Cell"), - menu: { - id: MenuId.NotebookCellTitle, - when: NOTEBOOK_EDITOR_FOCUSED, - group: CellOverflowToolbarGroups.Copy, - }, - keybinding: platform.isNative ? undefined : { - primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const clipboardService = accessor.get(IClipboardService); - const notebookService = accessor.get(INotebookService); - if (context.notebookEditor.hasOutputTextSelection()) { - document.execCommand('copy'); - return; + // if (result) { + // // deletion succeeds, move focus to the next cell + // const nextCellIdx = targetCellIndex < context.notebookEditor.viewModel.length ? targetCellIndex : context.notebookEditor.viewModel.length - 1; + // if (nextCellIdx >= 0) { + // context.notebookEditor.focusNotebookCell(context.notebookEditor.viewModel.viewCells[nextCellIdx], 'container'); + // } + // } } - - clipboardService.writeText(context.cell.getText()); - notebookService.setToCopy([context.cell.model], true); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: CUT_CELL_COMMAND_ID, - title: localize('notebookActions.cut', "Cut Cell"), - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), - group: CellOverflowToolbarGroups.Copy, - }, - keybinding: platform.isNative ? undefined : { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_X, - win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }, - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const clipboardService = accessor.get(IClipboardService); - const notebookService = accessor.get(INotebookService); - clipboardService.writeText(context.cell.getText()); - const viewModel = context.notebookEditor.viewModel; - - if (!viewModel || !viewModel.metadata.editable) { - return; - } - - viewModel.deleteCell(viewModel.getCellIndex(context.cell), true); - notebookService.setToCopy([context.cell.model], false); - } -}); - -registerAction2(class extends NotebookAction { - constructor() { - super( - { - id: PASTE_CELL_COMMAND_ID, - title: localize('notebookActions.paste', "Paste Cell"), - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE), - group: CellOverflowToolbarGroups.Copy, - }, - keybinding: platform.isNative ? undefined : { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, - linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext) { - const notebookService = accessor.get(INotebookService); - const pasteCells = notebookService.getToCopy(); - - const viewModel = context.notebookEditor.viewModel; - - if (!viewModel || !viewModel.metadata.editable) { - return; - } - - if (!pasteCells) { - return; - } - - const currCellIndex = context.cell && viewModel.getCellIndex(context.cell); - - let topPastedCell: CellViewModel | undefined = undefined; - pasteCells.items.reverse().map(cell => { - return { - source: cell.getValue(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: { - editable: cell.metadata?.editable, - breakpointMargin: cell.metadata?.breakpointMargin, - hasExecutionOrder: cell.metadata?.hasExecutionOrder, - inputCollapsed: cell.metadata?.inputCollapsed, - outputCollapsed: cell.metadata?.outputCollapsed, - custom: cell.metadata?.custom - } - }; - }).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); - }); - - if (topPastedCell) { - context.notebookEditor.focusNotebookCell(topPastedCell, 'container'); - } - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: PASTE_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.pasteAbove', "Paste Cell Above"), - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V, - weight: EDITOR_WIDGET_ACTION_WEIGHT - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - const notebookService = accessor.get(INotebookService); - const pasteCells = notebookService.getToCopy(); - - const viewModel = context.notebookEditor.viewModel; - - if (!viewModel || !viewModel.metadata.editable) { - return; - } - - if (!pasteCells) { - return; - } - - const currCellIndex = viewModel.getCellIndex(context.cell); - - let topPastedCell: CellViewModel | undefined = undefined; - pasteCells.items.reverse().map(cell => { - return { - source: cell.getValue(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: { - editable: cell.metadata?.editable, - breakpointMargin: cell.metadata?.breakpointMargin, - hasExecutionOrder: cell.metadata?.hasExecutionOrder, - inputCollapsed: cell.metadata?.inputCollapsed, - outputCollapsed: cell.metadata?.outputCollapsed, - custom: cell.metadata?.custom - } - }; - }).forEach(pasteCell => { - topPastedCell = viewModel.createCell(currCellIndex, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); - return; - }); - - if (topPastedCell) { - context.notebookEditor.focusNotebookCell(topPastedCell, 'container'); - } - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: COPY_CELL_UP_COMMAND_ID, - title: localize('notebookActions.copyCellUp', "Copy Cell Up"), - keybinding: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return copyCell(context, 'up'); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: COPY_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.copyCellDown', "Copy Cell Down"), - keybinding: { - primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return copyCell(context, 'down'); } }); @@ -1339,7 +1095,7 @@ registerAction2(class extends NotebookCellAction { NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.DownArrow, - weight: EDITOR_WIDGET_ACTION_WEIGHT + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }, { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED), @@ -1385,7 +1141,7 @@ registerAction2(class extends NotebookCellAction { NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.UpArrow, - weight: EDITOR_WIDGET_ACTION_WEIGHT + weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT }, }); } @@ -1780,101 +1536,6 @@ registerAction2(class extends NotebookAction { } }); -async function splitCell(context: INotebookCellActionContext): Promise { - const newCells = await context.notebookEditor.splitNotebookCell(context.cell); - if (newCells) { - context.notebookEditor.focusNotebookCell(newCells[newCells.length - 1], 'editor'); - } -} - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: SPLIT_CELL_COMMAND_ID, - title: localize('notebookActions.splitCell', "Split Cell"), - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), - order: CellToolbarOrder.SplitCell, - group: CELL_TITLE_CELL_GROUP_ID, - // alt: { - // id: JOIN_CELL_BELOW_COMMAND_ID, - // title: localize('notebookActions.joinCellBelow', "Join with Next Cell") - // } - }, - icon: icons.splitCellIcon, - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_EDITOR_CURSOR_BEGIN_END.toNegated()), - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH), - weight: KeybindingWeight.WorkbenchContrib - }, - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return splitCell(context); - } -}); - - -async function joinCells(context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { - const cell = await context.notebookEditor.joinNotebookCells(context.cell, direction); - if (cell) { - context.notebookEditor.focusNotebookCell(cell, 'editor'); - } -} - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: JOIN_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), - keybinding: { - when: NOTEBOOK_EDITOR_FOCUSED, - primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KEY_J, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), - group: CellOverflowToolbarGroups.Edit, - order: 10 - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return joinCells(context, 'above'); - } -}); - -registerAction2(class extends NotebookCellAction { - constructor() { - super( - { - id: JOIN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), - keybinding: { - when: NOTEBOOK_EDITOR_FOCUSED, - primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_J, - weight: KeybindingWeight.WorkbenchContrib - }, - menu: { - id: MenuId.NotebookCellTitle, - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), - group: CellOverflowToolbarGroups.Edit, - order: 11 - } - }); - } - - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext) { - return joinCells(context, 'below'); - } -}); - registerAction2(class extends NotebookCellAction { constructor() { super({ diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts index 1e0aa2bb9ec..73eeb0fab5c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts @@ -14,7 +14,7 @@ suite('Notebook Folding', () => { instantiationService.spy(IUndoRedoService, 'pushElement'); test('Folding based on markdown cells', async function () { - await withTestNotebook(instantiationService, + await withTestNotebook( [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -24,7 +24,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingController = new FoldingModel(); foldingController.attachViewModel(viewModel); @@ -41,7 +42,6 @@ suite('Notebook Folding', () => { test('Top level header in a cell wins', async function () { await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -51,7 +51,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingController = new FoldingModel(); foldingController.attachViewModel(viewModel); @@ -73,7 +74,6 @@ suite('Notebook Folding', () => { test('Folding', async function () { await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -83,7 +83,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); @@ -95,7 +96,6 @@ suite('Notebook Folding', () => { ); await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -105,7 +105,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); @@ -118,7 +119,6 @@ suite('Notebook Folding', () => { ); await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -128,7 +128,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 2, true); @@ -143,7 +144,6 @@ suite('Notebook Folding', () => { test('Nested Folding', async function () { await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -153,7 +153,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); updateFoldingStateAtIndex(foldingModel, 0, true); @@ -198,7 +199,6 @@ suite('Notebook Folding', () => { test('Folding Memento', async function () { await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -213,7 +213,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); @@ -227,7 +228,6 @@ suite('Notebook Folding', () => { ); await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -242,7 +242,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ @@ -260,7 +261,6 @@ suite('Notebook Folding', () => { ); await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -275,7 +275,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ @@ -295,7 +296,6 @@ suite('Notebook Folding', () => { test('View Index', async function () { await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -310,7 +310,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([{ start: 2, end: 6 }]); @@ -332,7 +333,6 @@ suite('Notebook Folding', () => { ); await withTestNotebook( - instantiationService, [ ['# header 1', 'markdown', CellKind.Markdown, [], {}], ['body', 'markdown', CellKind.Markdown, [], {}], @@ -347,7 +347,8 @@ suite('Notebook Folding', () => { ['## header 2.2', 'markdown', CellKind.Markdown, [], {}], ['var e = 7;', 'markdown', CellKind.Markdown, [], {}], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); foldingModel.applyMemento([ diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 1d789cf6d59..979399fdf6d 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -390,8 +390,8 @@ export class NotebookCellOutline implements IOutline { includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); } - const selectedCellIndex = viewModel.getSelection().start; - const selected = viewModel.getCellByIndex(selectedCellIndex)?.handle; + const focusedCellIndex = viewModel.getFocus().start; + const focused = viewModel.getCellByIndex(focusedCellIndex)?.handle; const entries: OutlineEntry[] = []; for (let i = 0; i < viewModel.viewCells.length; i++) { @@ -434,7 +434,7 @@ export class NotebookCellOutline implements IOutline { entries.push(new OutlineEntry(entries.length, 7, cell, preview, isMarkdown ? Codicon.markdown : Codicon.code)); } - if (cell.handle === selected) { + if (cell.handle === focused) { this._activeEntry = entries[entries.length - 1]; } @@ -518,7 +518,7 @@ export class NotebookCellOutline implements IOutline { const { viewModel } = this._editor; if (viewModel) { - const cell = viewModel.getCellByIndex(viewModel.getSelection().start); + const cell = viewModel.getCellByIndex(viewModel.getFocus().start); if (cell) { for (let entry of this._entries) { newActive = entry.find(cell, []); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/test/notebookOutline.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/test/notebookOutline.test.ts index 3d9bcfa2640..f06e3f8a775 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/test/notebookOutline.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/test/notebookOutline.test.ts @@ -30,7 +30,7 @@ suite('Notebook Outline', function () { }); function withNotebookOutline(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (outline: NotebookCellOutline, editor: IActiveNotebookEditor) => R): Promise { - return withTestNotebook(instantiationService, cells, (editor) => { + return withTestNotebook(cells, (editor) => { if (!editor.hasModel()) { assert.ok(false, 'MUST have active text editor'); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts index 6dec017a878..6ee60d654ab 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts @@ -5,7 +5,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; -import { INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { CellKind, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -28,7 +28,7 @@ class NotebookClipboardContribution extends Disposable implements INotebookEdito cellRangesToIndexes(visibleRanges).forEach(index => { const cell = this._notebookEditor.viewModel?.viewCells[index]; - if (cell?.cellKind === CellKind.Markdown) { + if (cell?.cellKind === CellKind.Markdown && cell?.editState === CellEditState.Preview) { this._notebookEditor.createMarkdownPreview(cell); } }); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index bc1637cac4d..f96d8ca6c4d 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -143,7 +143,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD markdownCellDrag(cellId: string, position: { clientY: number }): void { // throw new Error('Method not implemented.'); } - markdownCellDragEnd(cellId: string, position: { clientY: number }): void { + markdownCellDragEnd(cellId: string): void { + // throw new Error('Method not implemented.'); + } + markdownCellDrop(cellId: string) { // throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 281f36eb0c3..a85c8b7e8db 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -177,6 +177,10 @@ word-wrap: break-word; } +.monaco-workbench .notebookOverlay .output > div.foreground.error .output-stream { + color: red; /*TODO@rebornix theme color*/ +} + .monaco-workbench .notebookOverlay .cell-drag-image .output .multi-mimetype-output { display: none; } @@ -196,7 +200,7 @@ } .monaco-workbench .notebookOverlay .output .error_message { - color: red; + color: red; /*TODO@rebornix theme color*/ } .monaco-workbench .notebookOverlay .output .error > div { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 67c129064b9..8f277faeb04 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -66,6 +66,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/marker/markerProvider'; import 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import 'vs/workbench/contrib/notebook/browser/contrib/status/editorStatus'; import 'vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo'; +import 'vs/workbench/contrib/notebook/browser/contrib/cellOperations/cellOperations'; import 'vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown'; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ecee2ca9f0f..9e3c54e6c61 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { RunStateRenderer, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernel, ICellRange, IOrderedMimeType, INotebookRendererInfo, ICellOutput, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, INotebookKernel, ICellRange, IOrderedMimeType, INotebookRendererInfo, ICellOutput, IOutputItemDto, cellRangesToIndexes, reduceRanges } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; @@ -166,7 +166,8 @@ export interface ICommonNotebookEditor { setMarkdownCellEditState(cellId: string, editState: CellEditState): void; markdownCellDragStart(cellId: string, position: { clientY: number }): void; markdownCellDrag(cellId: string, position: { clientY: number }): void; - markdownCellDragEnd(cellId: string, position: { clientY: number, ctrlKey: boolean, altKey: boolean }): void; + markdownCellDrop(cellId: string, position: { clientY: number, ctrlKey: boolean, altKey: boolean }): void; + markdownCellDragEnd(cellId: string): void; } //#endregion @@ -231,6 +232,7 @@ export interface ICellViewModel extends IGenericCellViewModel { readonly model: NotebookCellTextModel; readonly id: string; readonly textBuffer: IReadonlyTextBuffer; + readonly layoutInfo: { totalHeight: number; }; dragging: boolean; handle: number; uri: URI; @@ -318,8 +320,7 @@ export interface INotebookEditorCreationOptions { export interface IActiveNotebookEditor extends INotebookEditor { viewModel: NotebookViewModel; - // selection is never undefined when the editor is attached to a document. - getSelection(): ICellRange; + getFocus(): ICellRange; } export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; @@ -331,7 +332,6 @@ export interface INotebookEditor extends ICommonNotebookEditor { // from the old IEditor readonly onDidChangeVisibleRanges: Event; readonly onDidChangeSelection: Event; - getSelection(): ICellRange | undefined; getSelections(): ICellRange[]; visibleRanges: ICellRange[]; textModel?: NotebookTextModel; @@ -411,11 +411,6 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ splitNotebookCell(cell: ICellViewModel): Promise; - /** - * Joins the given cell either with the cell above or the one below depending on the given direction. - */ - joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise; - /** * Delete a cell from the notebook */ @@ -571,6 +566,23 @@ export interface INotebookEditor extends ICommonNotebookEditor { */ revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise; + /** + * Get the view index of a cell + */ + getViewIndex(cell: ICellViewModel): number; + + /** + * @param startIndex Inclusive + * @param endIndex Exclusive + */ + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined; + + /** + * @param startIndex Inclusive + * @param endIndex Exclusive + */ + getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[]; + /** * Set hidden areas on cell text models. */ @@ -615,6 +627,7 @@ export interface INotebookEditor extends ICommonNotebookEditor { export interface INotebookCellList { isDisposed: boolean; + viewModel: NotebookViewModel | null; readonly contextKeyService: IContextKeyService; element(index: number): ICellViewModel | undefined; elementAt(position: number): ICellViewModel | undefined; @@ -640,6 +653,9 @@ export interface INotebookCellList { attachViewModel(viewModel: NotebookViewModel): void; clear(): void; getViewIndex(cell: ICellViewModel): number | undefined; + getViewIndex2(modelIndex: number): number | undefined; + getModelIndex(cell: CellViewModel): number | undefined; + getModelIndex2(viewIndex: number): number | undefined; getVisibleRangesPlusViewportAboveBelow(): ICellRange[]; focusElement(element: ICellViewModel): void; selectElement(element: ICellViewModel): void; @@ -883,3 +899,34 @@ export function updateEditorTopPadding(top: number) { export function getEditorTopPadding() { return EDITOR_TOP_PADDING; } + +/** + * ranges: model selections + * this will convert model selections to view indexes first, and then include the hidden ranges in the list view + */ +export function expandCellRangesWithHiddenCells(editor: INotebookEditor, viewModel: NotebookViewModel, ranges: ICellRange[]) { + // assuming ranges are sorted and no overlap + const indexes = cellRangesToIndexes(ranges); + let modelRanges: ICellRange[] = []; + indexes.forEach(index => { + const viewCell = viewModel.viewCells[index]; + + if (!viewCell) { + return; + } + + const viewIndex = editor.getViewIndex(viewCell); + if (viewIndex < 0) { + return; + } + + const nextViewIndex = viewIndex + 1; + const range = editor.getCellRangeFromViewRange(viewIndex, nextViewIndex); + + if (range) { + modelRanges.push(range); + } + }); + + return reduceRanges(modelRanges); +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 711238ca5c0..5d544864fda 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -5,6 +5,7 @@ import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IAction } from 'vs/base/common/actions'; @@ -66,9 +67,123 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { isWeb } from 'vs/base/common/platform'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; const $ = DOM.$; +export class ListViewInfoAccessor extends Disposable { + constructor( + readonly list: INotebookCellList + ) { + super(); + } + + revealCellRangeInView(range: ICellRange) { + return this.list.revealElementsInView(range); + } + + revealInView(cell: ICellViewModel) { + this.list.revealElementInView(cell); + } + + revealInViewAtTop(cell: ICellViewModel) { + this.list.revealElementInViewAtTop(cell); + } + + revealInCenterIfOutsideViewport(cell: ICellViewModel) { + this.list.revealElementInCenterIfOutsideViewport(cell); + } + + async revealInCenterIfOutsideViewportAsync(cell: ICellViewModel) { + return this.list.revealElementInCenterIfOutsideViewportAsync(cell); + } + + revealInCenter(cell: ICellViewModel) { + this.list.revealElementInCenter(cell); + } + + async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { + return this.list.revealElementLineInViewAsync(cell, line); + } + + async revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise { + return this.list.revealElementLineInCenterAsync(cell, line); + } + + async revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise { + return this.list.revealElementLineInCenterIfOutsideViewportAsync(cell, line); + } + + async revealRangeInViewAsync(cell: ICellViewModel, range: Range): Promise { + return this.list.revealElementRangeInViewAsync(cell, range); + } + + async revealRangeInCenterAsync(cell: ICellViewModel, range: Range): Promise { + return this.list.revealElementRangeInCenterAsync(cell, range); + } + + async revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise { + return this.list.revealElementRangeInCenterIfOutsideViewportAsync(cell, range); + } + + getViewIndex(cell: ICellViewModel): number { + return this.list.getViewIndex(cell) ?? -1; + } + + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined { + if (!this.list.viewModel) { + return undefined; + } + + const modelIndex = this.list.getModelIndex2(startIndex); + if (modelIndex === undefined) { + throw new Error(`startIndex ${startIndex} out of boundary`); + } + + if (endIndex >= this.list.length) { + // it's the end + const endModelIndex = this.list.viewModel.length; + return { start: modelIndex, end: endModelIndex }; + } else { + const endModelIndex = this.list.getModelIndex2(endIndex); + if (endModelIndex === undefined) { + throw new Error(`endIndex ${endIndex} out of boundary`); + } + return { start: modelIndex, end: endModelIndex }; + } + } + + getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[] { + if (!this.list.viewModel) { + return []; + } + + const range = this.getCellRangeFromViewRange(startIndex, endIndex); + if (!range) { + return []; + } + + return this.list.viewModel.viewCells.slice(range.start, range.end); + } + + setCellEditorSelection(cell: ICellViewModel, range: Range): void { + this.list.setCellSelection(cell, range); + } + + setHiddenAreas(_ranges: ICellRange[]): boolean { + return this.list.setHiddenAreas(_ranges, true); + } + + getVisibleRangesPlusViewportAboveBelow(): ICellRange[] { + return this.list?.getVisibleRangesPlusViewportAboveBelow() ?? []; + } + + triggerScroll(event: IMouseWheelEvent) { + this.list.triggerScrollFromMouseWheelEvent(event); + } +} + export class NotebookEditorWidget extends Disposable implements INotebookEditor { private static readonly EDITOR_MEMENTOS = new Map>(); private _overlayContainer!: HTMLElement; @@ -78,6 +193,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _webviewResolvePromise: Promise | null> | null = null; private _webviewTransparentCover: HTMLElement | null = null; private _list!: INotebookCellList; + private _listViewInfoAccessor!: ListViewInfoAccessor; private _dndController: CellDragAndDropController | null = null; private _listTopCellToolbar: ListTopCellToolbar | null = null; private _renderedEditors: Map = new Map(); @@ -107,6 +223,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _isDisposed: boolean = false; + private useRenderer = false; + get isDisposed() { return this._isDisposed; } @@ -199,6 +317,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor readonly creationOptions: INotebookEditorCreationOptions, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, + @IAccessibilityService accessibilityService: IAccessibilityService, @INotebookService private notebookService: INotebookService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -213,6 +332,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor super(); this.isEmbedded = creationOptions.isEmbedded || false; + this.useRenderer = this.configurationService.getValue('notebook.experimental.useMarkdownRenderer') ?? (!isWeb && !accessibilityService.isScreenReaderOptimized()); + this._overlayContainer = document.createElement('div'); this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); @@ -319,8 +440,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel?.getSelections() ?? []; } - getSelection() { - return this.viewModel?.getSelection(); + getFocus() { + return this.viewModel?.getFocus() ?? { start: 0, end: 0 }; } getSelectionViewModels(): ICellViewModel[] { @@ -405,7 +526,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const getScopedContextKeyService = (container: HTMLElement) => this._list.contextKeyService.createScoped(container); const renderers = [ this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService), - this.instantiationService.createInstance(MarkdownCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService), + this.instantiationService.createInstance(MarkdownCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService, { useRenderer: this.useRenderer }), ]; this._list = this.instantiationService.createInstance( @@ -424,6 +545,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor keyboardSupport: false, mouseSupport: true, multipleSelectionSupport: true, + selectionNavigation: true, enableKeyboardNavigation: true, additionalScrollHeight: 0, transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', @@ -446,7 +568,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor listInactiveFocusOutline: editorBackground, }, accessibilityProvider: { - getAriaLabel() { return null; }, + getAriaLabel: (element) => { + if (!this.viewModel) { + return ''; + } + const index = this.viewModel.getCellIndex(element); + + if (index >= 0) { + return `Cell ${index}, ${element.cellKind === CellKind.Markdown ? 'markdown' : 'code'} cell`; + } + + return ''; + }, getWidgetAriaLabel() { return nls.localize('notebookTreeAriaLabel', "Notebook"); } @@ -462,6 +595,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor // create Webview this._register(this._list); + this._listViewInfoAccessor = new ListViewInfoAccessor(this._list); + this._register(this._listViewInfoAccessor); + this._register(combinedDisposable(...renderers)); // top cell toolbar @@ -912,9 +1048,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor })); // init rendering - const useRenderer = this.configurationService.getValue('notebook.experimental.useMarkdownRenderer'); - - if (useRenderer) { + if (this.useRenderer) { await this._warmupWithMarkdownRenderer(this.viewModel, viewState); } else { this._list.attachViewModel(this.viewModel); @@ -1039,6 +1173,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } else if (this._list.length > 0) { this.viewModel?.updateSelectionsState({ kind: SelectionStateType.Index, + focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); } @@ -1236,67 +1371,82 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } revealCellRangeInView(range: ICellRange) { - return this._list.revealElementsInView(range); + return this._listViewInfoAccessor.revealCellRangeInView(range); } revealInView(cell: ICellViewModel) { - this._list.revealElementInView(cell); + this._listViewInfoAccessor.revealInView(cell); } revealInViewAtTop(cell: ICellViewModel) { - this._list.revealElementInViewAtTop(cell); + this._listViewInfoAccessor.revealInViewAtTop(cell); } revealInCenterIfOutsideViewport(cell: ICellViewModel) { - this._list.revealElementInCenterIfOutsideViewport(cell); + this._listViewInfoAccessor.revealInCenterIfOutsideViewport(cell); } async revealInCenterIfOutsideViewportAsync(cell: ICellViewModel) { - return this._list.revealElementInCenterIfOutsideViewportAsync(cell); + return this._listViewInfoAccessor.revealInCenterIfOutsideViewportAsync(cell); } revealInCenter(cell: ICellViewModel) { - this._list.revealElementInCenter(cell); + this._listViewInfoAccessor.revealInCenter(cell); } async revealLineInViewAsync(cell: ICellViewModel, line: number): Promise { - return this._list.revealElementLineInViewAsync(cell, line); + return this._listViewInfoAccessor.revealLineInViewAsync(cell, line); } async revealLineInCenterAsync(cell: ICellViewModel, line: number): Promise { - return this._list.revealElementLineInCenterAsync(cell, line); + return this._listViewInfoAccessor.revealLineInCenterAsync(cell, line); } async revealLineInCenterIfOutsideViewportAsync(cell: ICellViewModel, line: number): Promise { - return this._list.revealElementLineInCenterIfOutsideViewportAsync(cell, line); + return this._listViewInfoAccessor.revealLineInCenterIfOutsideViewportAsync(cell, line); } async revealRangeInViewAsync(cell: ICellViewModel, range: Range): Promise { - return this._list.revealElementRangeInViewAsync(cell, range); + return this._listViewInfoAccessor.revealRangeInViewAsync(cell, range); } async revealRangeInCenterAsync(cell: ICellViewModel, range: Range): Promise { - return this._list.revealElementRangeInCenterAsync(cell, range); + return this._listViewInfoAccessor.revealRangeInCenterAsync(cell, range); } async revealRangeInCenterIfOutsideViewportAsync(cell: ICellViewModel, range: Range): Promise { - return this._list.revealElementRangeInCenterIfOutsideViewportAsync(cell, range); + return this._listViewInfoAccessor.revealRangeInCenterIfOutsideViewportAsync(cell, range); + } + + getViewIndex(cell: ICellViewModel): number { + if (!this._listViewInfoAccessor) { + return -1; + } + return this._listViewInfoAccessor.getViewIndex(cell); + } + + getCellRangeFromViewRange(startIndex: number, endIndex: number): ICellRange | undefined { + return this._listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex); + } + + getCellsFromViewRange(startIndex: number, endIndex: number): ICellViewModel[] { + return this._listViewInfoAccessor.getCellsFromViewRange(startIndex, endIndex); } setCellEditorSelection(cell: ICellViewModel, range: Range): void { - this._list.setCellSelection(cell, range); - } - - changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { - return this.viewModel?.changeModelDecorations(callback) || null; + this._listViewInfoAccessor.setCellEditorSelection(cell, range); } setHiddenAreas(_ranges: ICellRange[]): boolean { - return this._list.setHiddenAreas(_ranges, true); + return this._listViewInfoAccessor.setHiddenAreas(_ranges); } getVisibleRangesPlusViewportAboveBelow(): ICellRange[] { - return this._list?.getVisibleRangesPlusViewportAboveBelow() ?? []; + return this._listViewInfoAccessor.getVisibleRangesPlusViewportAboveBelow(); + } + + triggerScroll(event: IMouseWheelEvent) { + this._listViewInfoAccessor.triggerScroll(event); } //#endregion @@ -1367,6 +1517,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webview?.deltaCellOutputContainerClassNames(cellId, added, removed); } + changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { + return this.viewModel?.changeModelDecorations(callback) || null; + } + //#endregion //#region Mouse Events @@ -1532,31 +1686,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel.splitNotebookCell(index); } - async joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise { - if (!this.viewModel) { - return null; - } - - if (!this.viewModel.metadata.editable) { - return null; - } - - const index = this.viewModel.getCellIndex(cell); - const ret = await this.viewModel.joinNotebookCells(index, direction, constraint); - - if (ret) { - ret.deletedCells.forEach(cell => { - if (this._pendingLayouts.has(cell)) { - this._pendingLayouts.get(cell)!.dispose(); - } - }); - - return ret.cell; - } else { - return null; - } - } - async deleteNotebookCell(cell: ICellViewModel): Promise { if (!this.viewModel) { return false; @@ -1678,6 +1807,27 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return undefined; } + private _cellFocusAria(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { + const index = this._notebookViewModel?.getCellIndex(cell); + + if (index !== undefined && index >= 0) { + let position = ''; + switch (focusItem) { + case 'editor': + position = `the inner ${cell.cellKind === CellKind.Markdown ? 'markdown' : 'code'} editor is focused, press escape to focus the cell container`; + break; + case 'output': + position = `the cell output is focused, press escape to focus the cell container`; + break; + case 'container': + position = `the ${cell.cellKind === CellKind.Markdown ? 'markdown preview' : 'cell container'} is focused, press enter to focus the inner ${cell.cellKind === CellKind.Markdown ? 'markdown' : 'code'} editor`; + break; + default: + break; + } + aria.alert(`Cell ${this._notebookViewModel?.getCellIndex(cell)}, ${position} `); + } + } focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output', options?: IFocusNotebookCellOptions) { if (this._isDisposed) { return; @@ -1685,6 +1835,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (focusItem === 'editor') { this.focusElement(cell); + this._cellFocusAria(cell, focusItem); this._list.focusView(); cell.editState = CellEditState.Editing; @@ -1694,6 +1845,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } else if (focusItem === 'output') { this.focusElement(cell); + this._cellFocusAria(cell, focusItem); this._list.focusView(); if (!this._webview) { @@ -1716,6 +1868,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor cell.focusMode = CellFocusMode.Container; this.focusElement(cell); + this._cellFocusAria(cell, focusItem); if (!options?.skipReveal) { this.revealInCenterIfOutsideViewport(cell); } @@ -1765,13 +1918,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor }; } - triggerScroll(event: IMouseWheelEvent) { - this._list.triggerScrollFromMouseWheelEvent(event); - } - async createMarkdownPreview(cell: MarkdownCellViewModel) { - const useRenderer = this.configurationService.getValue('notebook.experimental.useMarkdownRenderer'); - if (!useRenderer) { + if (!this.useRenderer) { // TODO: handle case where custom renderer is disabled? return; } @@ -1932,31 +2080,36 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor setMarkdownCellEditState(cellId: string, editState: CellEditState): void { const cell = this.getCellById(cellId); - if (cell && cell instanceof MarkdownCellViewModel) { + if (cell instanceof MarkdownCellViewModel) { cell.editState = editState; } } markdownCellDragStart(cellId: string, ctx: { clientY: number }): void { const cell = this.getCellById(cellId); - if (cell && cell instanceof MarkdownCellViewModel) { + if (cell instanceof MarkdownCellViewModel) { this._dndController?.startExplicitDrag(cell, ctx); } } markdownCellDrag(cellId: string, ctx: { clientY: number }): void { - const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId); - - if (cell && cell instanceof MarkdownCellViewModel) { + const cell = this.getCellById(cellId); + if (cell instanceof MarkdownCellViewModel) { this._dndController?.explicitDrag(cell, ctx); } } - markdownCellDragEnd(cellId: string, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }): void { - const cell = this.viewModel?.viewCells.find(vc => vc.id === cellId); + markdownCellDrop(cellId: string, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }): void { + const cell = this.getCellById(cellId); + if (cell instanceof MarkdownCellViewModel) { + this._dndController?.explicitDrop(cell, ctx); + } + } - if (cell && cell instanceof MarkdownCellViewModel) { - this._dndController?.endExplicitDrag(cell, ctx); + markdownCellDragEnd(cellId: string): void { + const cell = this.getCellById(cellId); + if (cell instanceof MarkdownCellViewModel) { + this._dndController?.endExplicitDrag(cell); } } @@ -2336,7 +2489,9 @@ 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:not(.webview-backed-markdown-cell) { padding-bottom: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_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: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_CELL_TOP_MARGIN}px; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container.webview-backed-markdown-cell { padding: 0; }`); + collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell .cell.code { 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); }`); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index e3f0ce91934..381a7828292 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -53,6 +53,9 @@ export class NotebookCellList extends WorkbenchList implements ID private readonly _onDidRemoveCellFromView = new Emitter(); readonly onDidRemoveCellFromView: Event = this._onDidRemoveCellFromView.event; private _viewModel: NotebookViewModel | null = null; + get viewModel(): NotebookViewModel | null { + return this._viewModel; + } private _hiddenRangeIds: string[] = []; private hiddenRangesPrefixSum: PrefixSumComputer | null = null; @@ -193,6 +196,10 @@ export class NotebookCellList extends WorkbenchList implements ID const top = this.getViewScrollTop(); const bottom = this.getViewScrollBottom(); + if (top >= bottom) { + return; + } + const topViewIndex = clamp(this.view.indexAt(top), 0, this.view.length - 1); const topElement = this.view.element(topViewIndex); const topModelIndex = this._viewModel!.getCellIndex(topElement); @@ -293,7 +300,7 @@ export class NotebookCellList extends WorkbenchList implements ID // convert model selections to view selections const viewSelections = cellRangesToIndexes(model.getSelections()).map(index => model.getCellByIndex(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); this.setSelection(viewSelections, undefined, true); - const primary = cellRangesToIndexes([model.getSelection()]).map(index => model.getCellByIndex(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); + const primary = cellRangesToIndexes([model.getFocus()]).map(index => model.getCellByIndex(index)).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); if (primary.length) { this.setFocus(primary, undefined, true); @@ -363,6 +370,8 @@ export class NotebookCellList extends WorkbenchList implements ID } if (!hasDifference) { + // they call 'setHiddenAreas' for a reason, even if the ranges are still the same, it's possible that the hiddenRangeSum is not update to date + this._updateHiddenRangePrefixSum(newRanges); return false; } } @@ -373,6 +382,16 @@ export class NotebookCellList extends WorkbenchList implements ID this._hiddenRangeIds = hiddenAreaIds; // set hidden ranges prefix sum + this._updateHiddenRangePrefixSum(newRanges); + + if (triggerViewUpdate) { + this.updateHiddenAreasInView(oldRanges, newRanges); + } + + return true; + } + + private _updateHiddenRangePrefixSum(newRanges: ICellRange[]) { let start = 0; let index = 0; const ret: number[] = []; @@ -397,12 +416,6 @@ export class NotebookCellList extends WorkbenchList implements ID } this.hiddenRangesPrefixSum = new PrefixSumComputer(values); - - if (triggerViewUpdate) { - this.updateHiddenAreasInView(oldRanges, newRanges); - } - - return true; } /** @@ -445,12 +458,30 @@ export class NotebookCellList extends WorkbenchList implements ID if (!selectionsLeft.length && this._viewModel!.viewCells.length) { // after splice, the selected cells are deleted - this._viewModel!.updateSelectionsState({ kind: SelectionStateType.Index, selections: [{ start: 0, end: 1 }] }); + this._viewModel!.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }); } } + getModelIndex(cell: CellViewModel): number | undefined { + const viewIndex = this.indexOf(cell); + return this.getModelIndex2(viewIndex); + } + + getModelIndex2(viewIndex: number): number | undefined { + if (!this.hiddenRangesPrefixSum) { + return viewIndex; + } + + const modelIndex = this.hiddenRangesPrefixSum.getAccumulatedValue(viewIndex - 1); + return modelIndex; + } + getViewIndex(cell: ICellViewModel) { const modelIndex = this._viewModel!.getCellIndex(cell); + return this.getViewIndex2(modelIndex); + } + + getViewIndex2(modelIndex: number): number | undefined { if (!this.hiddenRangesPrefixSum) { return modelIndex; } @@ -598,30 +629,12 @@ export class NotebookCellList extends WorkbenchList implements ID focusNext(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { this._focusNextPreviousDelegate.onFocusNext(() => { super.focusNext(n, loop, browserEvent, filter); - const focus = this.getFocus(); - if (focus.length) { - const focusedElementHandle = this.element(focus[0]).handle; - this._viewModel?.updateSelectionsState({ - kind: SelectionStateType.Handle, - primary: focusedElementHandle, - selections: [focusedElementHandle] - }, 'view'); - } }); } focusPrevious(n: number | undefined, loop: boolean | undefined, browserEvent?: UIEvent, filter?: (element: CellViewModel) => boolean): void { this._focusNextPreviousDelegate.onFocusPrevious(() => { super.focusPrevious(n, loop, browserEvent, filter); - const focus = this.getFocus(); - if (focus.length) { - const focusedElementHandle = this.element(focus[0]).handle; - this._viewModel?.updateSelectionsState({ - kind: SelectionStateType.Handle, - primary: focusedElementHandle, - selections: [focusedElementHandle] - }, 'view'); - } }); } @@ -701,7 +714,7 @@ export class NotebookCellList extends WorkbenchList implements ID const endElementHeight = this.view.elementHeight(endIndex); if (endElementTop >= wrapperBottom) { - return this._revealInternal(startIndex, false, CellRevealPosition.Top); + return this._revealInternal(endIndex, false, CellRevealPosition.Bottom); } if (endElementTop < wrapperBottom) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index 5c67edba3ac..ebf414449b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -153,7 +153,7 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut class StreamRendererContrib extends Disposable implements IOutputRendererContribution { getMimetypes() { - return ['application/x.notebook.stream']; + return ['application/x.notebook.stdout', 'application/x.notebook.stream']; } constructor( @@ -180,6 +180,19 @@ class StreamRendererContrib extends Disposable implements IOutputRendererContrib } } +class StderrRendererContrib extends StreamRendererContrib { + + getMimetypes() { + return ['application/x.notebook.stderr']; + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const result = super.render(output, items, container, notebookUri); + container.classList.add('error'); + return result; + } +} + class ErrorRendererContrib extends Disposable implements IOutputRendererContribution { getMimetypes() { @@ -392,6 +405,7 @@ NotebookRegistry.registerOutputTransform('plain', PlainTextRendererContrib); NotebookRegistry.registerOutputTransform('code', CodeRendererContrib); NotebookRegistry.registerOutputTransform('error-trace', ErrorRendererContrib); NotebookRegistry.registerOutputTransform('stream-text', StreamRendererContrib); +NotebookRegistry.registerOutputTransform('stderr', StderrRendererContrib); export function getOutputSimpleEditorOptions(): IEditorOptions { return { 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 a51ad899b8f..53a5f855cce 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -97,26 +97,35 @@ export interface IToggleMarkdownPreviewMessage extends BaseToWebviewMessage { export interface ICellDragStartMessage extends BaseToWebviewMessage { type: 'cell-drag-start'; - cellId: string; - position: { clientX: number, clientY: number }; + readonly cellId: string; + readonly position: { + readonly clientY: number; + }; } export interface ICellDragMessage extends BaseToWebviewMessage { type: 'cell-drag'; - cellId: string; - position: { clientX: number, clientY: number }; + readonly cellId: string; + readonly position: { + readonly clientY: number; + }; +} + +export interface ICellDropMessage extends BaseToWebviewMessage { + readonly type: 'cell-drop'; + readonly cellId: string; + readonly ctrlKey: boolean + readonly altKey: boolean; + readonly position: { + readonly clientY: number; + }; } export interface ICellDragEndMessage extends BaseToWebviewMessage { readonly type: 'cell-drag-end'; readonly cellId: string; - readonly ctrlKey: boolean - readonly altKey: boolean; - readonly position: { - readonly clientX: number; - readonly clientY: number; - }; } + export interface IInitializedMarkdownPreviewMessage extends BaseToWebviewMessage { readonly type: 'initializedMarkdownPreview'; } @@ -290,6 +299,7 @@ export type FromWebviewMessage = | IToggleMarkdownPreviewMessage | ICellDragStartMessage | ICellDragMessage + | ICellDropMessage | ICellDragEndMessage | IInitializedMarkdownPreviewMessage ; @@ -416,7 +426,7 @@ export class BackLayerWebView extends Disposable { /* markdown */ #container > div > div.preview { color: var(--vscode-foreground); - width: calc(100% - ${this.options.cellMargin}px); + width: 100%; padding-left: ${this.options.leftMargin}px; padding-top: ${this.options.previewNodePadding}px; padding-bottom: ${this.options.previewNodePadding}px; @@ -915,17 +925,21 @@ var requirejs = (function() { this.notebookEditor.markdownCellDrag(data.cellId, data.position); break; } - case 'cell-drag-end': + case 'cell-drop': { - this.notebookEditor.markdownCellDragEnd(data.cellId, { + this.notebookEditor.markdownCellDrop(data.cellId, { clientY: data.position.clientY, ctrlKey: data.ctrlKey, altKey: data.altKey, }); break; } + case 'cell-drag-end': + { + this.notebookEditor.markdownCellDragEnd(data.cellId); + break; + } } - })); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts index 5a9c55b3b89..f996743ab6c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellDnd.ts @@ -10,8 +10,9 @@ import { Delayer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { BOTTOM_CELL_TOOLBAR_GAP } from 'vs/workbench/contrib/notebook/browser/constants'; -import { BaseCellRenderTemplate, CellEditState, ICellViewModel, INotebookCellList, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BaseCellRenderTemplate, expandCellRangesWithHiddenCells, ICellViewModel, INotebookCellList, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { cloneNotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { CellEditType, cellRangesToIndexes, ICellRange, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const $ = DOM.$; @@ -175,26 +176,28 @@ export class CellDragAndDropController extends Disposable { return; } - let draggedCells: ICellViewModel[] = [draggedCell]; - let draggedCellRange: [number, number] = [this.notebookEditor.viewModel!.getCellIndex(draggedCell), 1]; - - if (draggedCell.cellKind === CellKind.Markdown) { - const currCellIndex = this.notebookEditor.viewModel!.getCellIndex(draggedCell); - const nextVisibleCellIndex = this.notebookEditor.viewModel!.getNextVisibleCellIndex(currCellIndex); - - if (nextVisibleCellIndex > currCellIndex + 1) { - // folding ;) - draggedCells = this.notebookEditor.viewModel!.viewCells.slice(currCellIndex, nextVisibleCellIndex); - draggedCellRange = [currCellIndex, nextVisibleCellIndex - currCellIndex]; - } - } - this.dragCleanup(); - const isCopy = (event.browserEvent.ctrlKey && !platform.isMacintosh) || (event.browserEvent.altKey && platform.isMacintosh); - const dropDirection = this.getDropInsertDirection(event.dragPosRatio); - const insertionIndicatorAbsolutePos = dropDirection === 'above' ? event.cellTop : event.cellTop + event.cellHeight; + this._dropImpl(draggedCell, dropDirection, event.browserEvent, event.draggedOverCell); + } + + private getCellRangeAroundDragTarget(draggedCellIndex: number) { + const selections = this.notebookEditor.getSelections(); + const modelRanges = expandCellRangesWithHiddenCells(this.notebookEditor, this.notebookEditor.viewModel!, selections); + const nearestRange = modelRanges.find(range => range.start <= draggedCellIndex && draggedCellIndex < range.end); + + if (nearestRange) { + return nearestRange; + } else { + return { start: draggedCellIndex, end: draggedCellIndex + 1 }; + } + } + + private _dropImpl(draggedCell: ICellViewModel, dropDirection: 'above' | 'below', ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }, draggedOverCell: ICellViewModel) { + const cellTop = this.list.getAbsoluteTopOfElement(draggedOverCell); + const cellHeight = this.list.elementHeight(draggedOverCell); + const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight; const insertionIndicatorTop = insertionIndicatorAbsolutePos - this.list.scrollTop + BOTTOM_CELL_TOOLBAR_GAP / 2; const editorHeight = this.notebookEditor.getDomNode().getBoundingClientRect().height; if (insertionIndicatorTop < 0 || insertionIndicatorTop > editorHeight) { @@ -202,18 +205,77 @@ export class CellDragAndDropController extends Disposable { return; } + const isCopy = (ctx.ctrlKey && !platform.isMacintosh) || (ctx.altKey && platform.isMacintosh); + if (isCopy) { - this.copyCells(draggedCells, event.draggedOverCell, dropDirection); - } else { const viewModel = this.notebookEditor.viewModel!; - let originalToIdx = viewModel.getCellIndex(event.draggedOverCell); + const draggedCellIndex = this.notebookEditor.viewModel!.getCellIndex(draggedCell); + const range = this.getCellRangeAroundDragTarget(draggedCellIndex); + + let originalToIdx = viewModel.getCellIndex(draggedOverCell); if (dropDirection === 'below') { - const relativeToIndex = viewModel.getCellIndex(event.draggedOverCell); + const relativeToIndex = viewModel.getCellIndex(draggedOverCell); const newIdx = viewModel.getNextVisibleCellIndex(relativeToIndex); originalToIdx = newIdx; } - this.notebookEditor.moveCellsToIdx(draggedCellRange[0], draggedCellRange[1], originalToIdx); + let finalSelection: ICellRange; + let finalFocus: ICellRange; + + if (originalToIdx <= range.start) { + finalSelection = { start: originalToIdx, end: originalToIdx + range.end - range.start }; + finalFocus = { start: originalToIdx + draggedCellIndex - range.start, end: originalToIdx + draggedCellIndex - range.start + 1 }; + } else { + const delta = (originalToIdx - range.start); + finalSelection = { start: range.start + delta, end: range.end + delta }; + finalFocus = { start: draggedCellIndex + delta, end: draggedCellIndex + delta + 1 }; + } + + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Replace, + index: originalToIdx, + count: 0, + cells: cellRangesToIndexes([range]).map(index => cloneNotebookCellTextModel(viewModel.viewCells[index].model)) + } + ], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: finalFocus, selections: [finalSelection] }), undefined, true); + this.notebookEditor.revealCellRangeInView(finalSelection); + } else { + const viewModel = this.notebookEditor.viewModel!; + const draggedCellIndex = this.notebookEditor.viewModel!.getCellIndex(draggedCell); + const range = this.getCellRangeAroundDragTarget(draggedCellIndex); + let originalToIdx = viewModel.getCellIndex(draggedOverCell); + if (dropDirection === 'below') { + const relativeToIndex = viewModel.getCellIndex(draggedOverCell); + const newIdx = viewModel.getNextVisibleCellIndex(relativeToIndex); + originalToIdx = newIdx; + } + + if (originalToIdx >= range.start && originalToIdx <= range.end) { + return; + } + + let finalSelection: ICellRange; + let finalFocus: ICellRange; + + if (originalToIdx <= range.start) { + finalSelection = { start: originalToIdx, end: originalToIdx + range.end - range.start }; + finalFocus = { start: originalToIdx + draggedCellIndex - range.start, end: originalToIdx + draggedCellIndex - range.start + 1 }; + } else { + const delta = (originalToIdx - range.end); + finalSelection = { start: range.start + delta, end: range.end + delta }; + finalFocus = { start: draggedCellIndex + delta, end: draggedCellIndex + delta + 1 }; + } + + viewModel.notebookDocument.applyEdits([ + { + editType: CellEditType.Move, + index: range.start, + length: range.end - range.start, + newIdx: originalToIdx <= range.start ? originalToIdx : (originalToIdx - (range.end - range.start)) + } + ], true, { kind: SelectionStateType.Index, focus: viewModel.getFocus(), selections: viewModel.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: finalFocus, selections: [finalSelection] }), undefined, true); + this.notebookEditor.revealCellRangeInView(finalSelection); } } @@ -259,27 +321,6 @@ export class CellDragAndDropController extends Disposable { })); } - private copyCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') { - this.notebookEditor.textModel!.pushStackElement('Copy Cells', undefined, undefined); - let firstNewCell: ICellViewModel | undefined = undefined; - let firstNewCellState: CellEditState = CellEditState.Preview; - for (let i = 0; i < draggedCells.length; i++) { - const draggedCell = draggedCells[i]; - const newCell = this.notebookEditor.insertNotebookCell(ontoCell, draggedCell.cellKind, direction, draggedCell.getText()); - - if (newCell && !firstNewCell) { - firstNewCell = newCell; - firstNewCellState = draggedCell.editState; - } - } - - if (firstNewCell) { - this.notebookEditor.focusNotebookCell(firstNewCell, firstNewCellState === CellEditState.Editing ? 'editor' : 'container'); - } - - this.notebookEditor.textModel!.pushStackElement('Copy Cells', undefined, undefined); - } - public startExplicitDrag(cell: ICellViewModel, position: { clientY: number }) { this.currentDraggedCell = cell; this.setInsertIndicatorVisibility(true); @@ -295,9 +336,30 @@ export class CellDragAndDropController extends Disposable { const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight; this.updateInsertIndicator(dropDirection, insertionIndicatorAbsolutePos); } + + // Try scrolling list if needed + if (this.currentDraggedCell !== cell) { + return; + } + + const viewRect = this.notebookEditor.getDomNode().getBoundingClientRect(); + const eventPositionInView = position.clientY - this.list.scrollTop; + + const scrollMargin = 0.2; + const maxScrollPerFrame = 20; + const eventPositionRatio = eventPositionInView / viewRect.height; + if (eventPositionRatio < scrollMargin) { + this.list.scrollTop -= maxScrollPerFrame * (1 - eventPositionRatio / scrollMargin); + } else if (eventPositionRatio > 1 - scrollMargin) { + this.list.scrollTop += maxScrollPerFrame * (1 - ((1 - eventPositionRatio) / scrollMargin)); + } } - public endExplicitDrag(cell: ICellViewModel, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }) { + public endExplicitDrag(_cell: ICellViewModel) { + this.setInsertIndicatorVisibility(false); + } + + public explicitDrop(cell: ICellViewModel, ctx: { clientY: number, ctrlKey: boolean, altKey: boolean }) { this.currentDraggedCell = undefined; this.setInsertIndicatorVisibility(false); @@ -308,24 +370,8 @@ export class CellDragAndDropController extends Disposable { const cellTop = this.list.getAbsoluteTopOfElement(target); const cellHeight = this.list.elementHeight(target); - const dropDirection = this.getExplicitDragDropDirection(ctx.clientY, cellTop, cellHeight); - - const isCopy = (ctx.ctrlKey && !platform.isMacintosh) || (ctx.altKey && platform.isMacintosh); - if (isCopy) { - this.copyCells([cell], target, dropDirection); - } else { - const viewModel = this.notebookEditor.viewModel!; - let originalToIdx = viewModel.getCellIndex(target); - if (dropDirection === 'below') { - const relativeToIndex = viewModel.getCellIndex(target); - const newIdx = viewModel.getNextVisibleCellIndex(relativeToIndex); - originalToIdx = newIdx; - } - - const draggedCellRange = [this.notebookEditor.viewModel!.getCellIndex(cell), 1]; - this.notebookEditor.moveCellsToIdx(draggedCellRange[0], draggedCellRange[1], originalToIdx); - } + this._dropImpl(cell, dropDirection, ctx, target); } private getExplicitDragDropDirection(clientY: number, cellTop: number, cellHeight: number) { 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 c6689e531c3..5300a88235f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -12,7 +12,7 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo import { CodeCellRenderTemplate, ICellOutputViewModel, IInsetRenderOutput, INotebookEditor, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { BUILTIN_RENDERER_ID, CellUri, NotebookCellOutputsSplice, IOrderedMimeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BUILTIN_RENDERER_ID, CellUri, NotebookCellOutputsSplice, IOrderedMimeType, mimeTypeIsAlwaysSecure } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; @@ -72,7 +72,9 @@ export class CellOutputElement extends Disposable { updateDOMTop(top: number) { if (this.useDedicatedDOM) { - this.domNode.style.top = `${top}px`; + if (this.domNode) { + this.domNode.style.top = `${top}px`; + } } } @@ -98,47 +100,37 @@ export class CellOutputElement extends Disposable { } const notebookTextModel = this.notebookEditor.viewModel.notebookDocument; - - let renderResult: IRenderOutput | undefined = undefined; - - // Reuse output item div - this.useDedicatedDOM = !(!beforeElement && this.output.supportAppend() && this.previousDivSupportAppend()); - this.domNode = this.useDedicatedDOM ? DOM.$('.output-inner-container') : this.outputContainer.lastChild as HTMLElement; - - if (this.output.supportAppend()) { - this.domNode.classList.add('support-append'); - } - const [mimeTypes, pick] = this.output.resolveMimeTypes(notebookTextModel); - if (!mimeTypes.find(mimeType => mimeType.isTrusted)) { + if (!mimeTypes.find(mimeType => mimeType.isTrusted) || mimeTypes.length === 0) { this.viewCell.updateOutputHeight(index, 0); return undefined; } + const pickedMimeTypeRenderer = mimeTypes[pick]; + // Reuse output item div + this.useDedicatedDOM = !(!beforeElement && this.output.supportAppend() && this.previousDivSupportAppend(pickedMimeTypeRenderer.mimeType)); + this.domNode = this.useDedicatedDOM ? DOM.$('.output-inner-container') : this.outputContainer.lastChild as HTMLElement; + this.domNode.setAttribute('output-mime-type', pickedMimeTypeRenderer.mimeType); + if (mimeTypes.filter(mimeType => mimeType.isTrusted).length > 1) { this.attachMimetypeSwitcher(this.domNode, notebookTextModel, mimeTypes); } - if (mimeTypes.length !== 0) { - const pickedMimeTypeRenderer = mimeTypes[pick]; - const notebookUri = CellUri.parse(this.viewCell.uri)?.notebook; + const notebookUri = CellUri.parse(this.viewCell.uri)?.notebook; - if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { - const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); - renderResult = renderer - ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } - : this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, notebookUri); - } else { - renderResult = this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, notebookUri); - } - - this.output.pickedMimeType = pick; + if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { + const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + this.renderResult = renderer + ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } + : this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, notebookUri); + } else { + this.renderResult = this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, notebookUri); } - this.renderResult = renderResult; + this.output.pickedMimeType = pick; - if (!renderResult) { + if (!this.renderResult) { this.viewCell.updateOutputHeight(index, 0); return undefined; } @@ -149,15 +141,15 @@ export class CellOutputElement extends Disposable { this.outputContainer.appendChild(this.domNode); } - if (renderResult.type !== RenderOutputType.Mainframe) { - this.notebookEditor.createOutput(this.viewCell, renderResult, this.viewCell.getOutputOffset(index)); + if (this.renderResult.type !== RenderOutputType.Mainframe) { + this.notebookEditor.createOutput(this.viewCell, this.renderResult, this.viewCell.getOutputOffset(index)); this.domNode.classList.add('background'); } else { this.domNode.classList.add('foreground', 'output-element'); this.domNode.style.position = 'absolute'; } - if (renderResult.type === RenderOutputType.Html || renderResult.type === RenderOutputType.Extension) { + if (this.renderResult.type === RenderOutputType.Html || this.renderResult.type === RenderOutputType.Extension) { // the output is rendered in the webview, which has resize listener internally // no-op return { initRenderIsSynchronous: false }; @@ -209,8 +201,14 @@ export class CellOutputElement extends Disposable { this.localDisposableStore.add(elementSizeObserver); } - private previousDivSupportAppend() { - return this.outputContainer.lastChild && (this.outputContainer.lastChild).classList.contains('support-append'); + private previousDivSupportAppend(mimeType: string) { + const lastChild = this.outputContainer.lastChild as HTMLElement | null; + + if (lastChild) { + return lastChild.getAttribute('output-mime-type') === mimeType; + } + + return false; } private async attachMimetypeSwitcher(outputItemDiv: HTMLElement, notebookTextModel: NotebookTextModel, mimeTypes: readonly IOrderedMimeType[]) { @@ -413,17 +411,7 @@ export class CellOutputContainer extends Disposable { // not trusted const secureOutput = outputs.filter(output => { const mimeTypes = output.model.outputs.map(op => op.mime); - - if (mimeTypes.indexOf('application/x.notebook.stream') >= 0 - || mimeTypes.indexOf('application/x.notebook.error-traceback') >= 0 - || mimeTypes.indexOf('text/plain') >= 0 - || mimeTypes.indexOf('text/markdown') >= 0 - || mimeTypes.indexOf('application/json') >= 0 - || mimeTypes.includes('image/png')) { - return true; - } - - return false; + return mimeTypes.some(mimeTypeIsAlwaysSecure); }); return secureOutput; 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 bf8ff84ece8..b3fdbf16f77 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -5,6 +5,7 @@ import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { domEvent } from 'vs/base/browser/event'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -375,11 +376,14 @@ abstract class AbstractCellRenderer { export class MarkdownCellRenderer extends AbstractCellRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'markdown_cell'; + private readonly useRenderer: boolean; + constructor( notebookEditor: INotebookEditor, dndController: CellDragAndDropController, private renderedEditors: Map, contextKeyServiceProvider: (container: HTMLElement) => IContextKeyService, + options: { useRenderer: boolean }, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -387,6 +391,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR @INotificationService notificationService: INotificationService, ) { super(instantiationService, notebookEditor, contextMenuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController); + this.useRenderer = options.useRenderer; } get templateId() { @@ -428,10 +433,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR const titleMenu = disposables.add(this.cellMenus.getCellTitleMenu(contextKeyService)); - const useRenderer = !!(this.configurationService.getValue('notebook.experimental.useMarkdownRenderer')); - const templateData: MarkdownCellRenderTemplate = { - useRenderer, + useRenderer: this.useRenderer, rootContainer, collapsedPart, expandButton, @@ -456,7 +459,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR toJSON: () => { return {}; } }; - if (!useRenderer) { + if (!this.useRenderer) { this.dndController.registerDragHandle(templateData, rootContainer, container, () => this.getDragImage(templateData)); } @@ -562,6 +565,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR this.setupCellToolbarActions(templateData, elementDisposables); const toolbarContext = { + ui: true, cell: element, notebookEditor: this.notebookEditor, $mid: 12 @@ -879,7 +883,13 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.updateExecutionOrder(metadata, templateData); templateData.statusBar.cellStatusMessageContainer.textContent = metadata?.statusMessage || ''; - templateData.cellRunState.renderState(element.metadata?.runState); + templateData.cellRunState.renderState(element.metadata?.runState, () => { + if (!this.notebookEditor.viewModel) { + return -1; + } + + return this.notebookEditor.viewModel.getCellIndex(element); + }); if (metadata.runState === NotebookCellRunState.Running) { if (metadata.runStartTime) { @@ -1015,6 +1025,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.setupCellToolbarActions(templateData, elementDisposables); const toolbarContext = { + ui: true, cell: element, cellTemplate: templateData, notebookEditor: this.notebookEditor, @@ -1093,6 +1104,7 @@ export class RunStateRenderer { private spinnerTimer: any | undefined; private pendingNewState: NotebookCellRunState | undefined; + private lastRunState: NotebookCellRunState | undefined; constructor(private readonly element: HTMLElement) { DOM.hide(element); @@ -1105,23 +1117,28 @@ export class RunStateRenderer { } } - renderState(runState: NotebookCellRunState = NotebookCellRunState.Idle) { + renderState(runState: NotebookCellRunState = NotebookCellRunState.Idle, getCellIndex: () => number) { if (this.spinnerTimer) { this.pendingNewState = runState; return; } if (runState === NotebookCellRunState.Success) { + aria.alert(`Code cell at ${getCellIndex()} finishes running successfully`); DOM.reset(this.element, renderIcon(successStateIcon)); } else if (runState === NotebookCellRunState.Error) { + aria.alert(`Code cell at ${getCellIndex()} finishes running with errors`); DOM.reset(this.element, renderIcon(errorStateIcon)); } else if (runState === NotebookCellRunState.Running) { + if (this.lastRunState !== NotebookCellRunState.Running) { + aria.alert(`Code cell at ${getCellIndex()} starts running`); + } DOM.reset(this.element, renderIcon(syncing)); this.spinnerTimer = setTimeout(() => { this.spinnerTimer = undefined; if (this.pendingNewState) { - this.renderState(this.pendingNewState); + this.renderState(this.pendingNewState, getCellIndex); this.pendingNewState = undefined; } }, RunStateRenderer.MIN_SPINNER_TIME); @@ -1134,6 +1151,8 @@ export class RunStateRenderer { } else { this.element.style.display = 'flex'; } + + this.lastRunState = runState; } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts index ffc9001117d..11fa2956367 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -76,9 +76,8 @@ export class CodeCell extends Disposable { }); const updateForFocusMode = () => { - if (this.notebookEditor.getSelection().start !== this.notebookEditor.viewModel.getCellIndex(viewCell)) { + if (this.notebookEditor.getFocus().start !== this.notebookEditor.viewModel.getCellIndex(viewCell)) { templateData.container.classList.toggle('cell-editor-focus', viewCell.focusMode === CellFocusMode.Editor); - return; } if (viewCell.focusMode === CellFocusMode.Editor) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 788a21834b8..e1111a8887a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -193,8 +193,13 @@ export class StatefulMarkdownCell extends Disposable { } })); - // Update for selection if (this.useRenderer) { + // the markdown preview's height might already be updated after the renderer calls `element.getHeight()` + if (this.viewCell.layoutInfo.totalHeight > 0) { + this.relayoutCell(); + } + + // Update for selection this._register(this.notebookEditor.onDidChangeSelection(() => { const selectedCells = this.notebookEditor.getSelectionViewModels(); const isSelected = selectedCells.length > 1 && selectedCells.some(selectedCell => selectedCell === viewCell); @@ -340,15 +345,16 @@ export class StatefulMarkdownCell extends Disposable { const editorContextKeyService = this.contextKeyService.createScoped(this.templateData.editorPart); EditorContextKeys.inCompositeEditor.bindTo(editorContextKeyService).set(true); const editorInstaService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, editorContextKeyService])); + this._register(editorContextKeyService); - this.editor = editorInstaService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { + this.editor = this._register(editorInstaService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { ...this.editorOptions, dimension: { width: width, height: editorHeight }, // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() - }, {}); + }, {})); this.templateData.currentEditor = this.editor; const cts = new CancellationTokenSource(); 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 88f8072a023..89400ecc8b7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -6,7 +6,7 @@ import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; 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'; +import { FromWebviewMessage, IBlurOutputMessage, ICellDropMessage, ICellDragMessage, ICellDragStartMessage, IClickedDataUrlMessage, ICustomRendererMessage, IDimensionMessage, IFocusMarkdownPreviewMessage, IMouseEnterMarkdownPreviewMessage, IMouseEnterMessage, IMouseLeaveMarkdownPreviewMessage, IMouseLeaveMessage, IToggleMarkdownPreviewMessage, IWheelMessage, ToWebviewMessage, ICellDragEndMessage } 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 @@ -437,9 +437,7 @@ function webviewPreloads() { cellContainer.style.visibility = 'visible'; } - if (typeof data.content === 'string') { - updateMarkdownPreview(data.id, data.content); - } + updateMarkdownPreview(data.id, data.content); } break; case 'hideMarkdownPreview': @@ -458,6 +456,7 @@ function webviewPreloads() { if (cellContainer) { cellContainer.style.visibility = 'visible'; } + updateMarkdownPreview(event.data.id, undefined); } break; case 'removeMarkdownPreview': @@ -696,29 +695,6 @@ function webviewPreloads() { type: 'initialized' }); - document.addEventListener('dragover', e => { - // Allow dropping dragged markdown cells - e.preventDefault(); - }); - - const markdownCellDragDataType = 'x-vscode-markdown-cell-drag'; - - document.addEventListener('drop', e => { - const data = e.dataTransfer?.getData(markdownCellDragDataType); - if (!data) { - return; - } - e.preventDefault(); - - const { cellId } = JSON.parse(data); - postNotebookMessage('cell-drag-end', { - cellId: cellId, - ctrlKey: e.ctrlKey, - altKey: e.altKey, - position: { clientX: e.clientX, clientY: e.clientY }, - }); - }); - function createMarkdownPreview(cellId: string, content: string, top: number) { const container = document.getElementById('container')!; const cellContainer = document.createElement('div'); @@ -751,28 +727,15 @@ function webviewPreloads() { previewContainerNode.setAttribute('draggable', 'true'); previewContainerNode.addEventListener('dragstart', e => { - if (!e.dataTransfer) { - return; - } - e.dataTransfer.setData(markdownCellDragDataType, JSON.stringify({ cellId })); - - (e.target as HTMLElement).classList.add('dragging'); - - postNotebookMessage('cell-drag-start', { - cellId: cellId, - position: { clientX: e.clientX, clientY: e.clientY }, - }); + markdownPreviewDragManager.startDrag(e, cellId); }); previewContainerNode.addEventListener('drag', e => { - postNotebookMessage('cell-drag', { - cellId: cellId, - position: { clientX: e.clientX, clientY: e.clientY }, - }); + markdownPreviewDragManager.updateDrag(e, cellId); }); previewContainerNode.addEventListener('dragend', e => { - (e.target as HTMLElement).classList.remove('dragging'); + markdownPreviewDragManager.endDrag(e, cellId); }); cellContainer.appendChild(previewContainerNode); @@ -809,18 +772,21 @@ function webviewPreloads() { }); } - function updateMarkdownPreview(cellId: string, content: string) { + function updateMarkdownPreview(cellId: string, content: string | undefined) { const previewContainerNode = document.getElementById(`${cellId}_preview`); if (!previewContainerNode) { return; } // TODO: handle namespace - onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, { - element: previewContainerNode, - content: content - }]); + if (typeof content === 'string') { + onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, { + element: previewContainerNode, + content: content + }]); + } + console.log(previewContainerNode.clientHeight); postNotebookMessage('dimension', { id: `${cellId}_preview`, data: { @@ -829,6 +795,85 @@ function webviewPreloads() { isOutput: false }); } + + const markdownCellDragDataType = 'x-vscode-markdown-cell-drag'; + + const markdownPreviewDragManager = new class MarkdownPreviewDragManager { + + private currentDrag: { cellId: string, clientY: number } | undefined; + + constructor() { + document.addEventListener('dragover', e => { + // Allow dropping dragged markdown cells + e.preventDefault(); + }); + + document.addEventListener('drop', e => { + e.preventDefault(); + this.currentDrag = undefined; + + const data = e.dataTransfer?.getData(markdownCellDragDataType); + if (!data) { + return; + } + + const { cellId } = JSON.parse(data); + postNotebookMessage('cell-drop', { + cellId: cellId, + ctrlKey: e.ctrlKey, + altKey: e.altKey, + position: { clientY: e.clientY }, + }); + }); + } + + startDrag(e: DragEvent, cellId: string) { + if (!e.dataTransfer) { + return; + } + + this.currentDrag = { cellId, clientY: e.clientY }; + + e.dataTransfer.setData(markdownCellDragDataType, JSON.stringify({ cellId })); + + (e.target as HTMLElement).classList.add('dragging'); + + postNotebookMessage('cell-drag-start', { + cellId: cellId, + position: { clientY: e.clientY }, + }); + + // Continuously send updates while dragging instead of relying on `updateDrag`. + // This lets us scroll the list based on drag position. + const trySendDragUpdate = () => { + if (this.currentDrag?.cellId !== cellId) { + return; + } + + postNotebookMessage('cell-drag', { + cellId: cellId, + position: { clientY: this.currentDrag.clientY }, + }); + requestAnimationFrame(trySendDragUpdate); + }; + requestAnimationFrame(trySendDragUpdate); + } + + updateDrag(e: DragEvent, cellId: string) { + if (cellId !== this.currentDrag?.cellId) { + this.currentDrag = undefined; + } + this.currentDrag = { cellId, clientY: e.clientY }; + } + + endDrag(e: DragEvent, cellId: string) { + this.currentDrag = undefined; + (e.target as HTMLElement).classList.remove('dragging'); + postNotebookMessage('cell-drag-end', { + cellId: cellId + }); + } + }(); } export function preloadsScriptStr(values: { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts index 5a4db12a75b..f5e9373c8a0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts @@ -6,7 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ICellOutputViewModel, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ICellOutput, IOrderedMimeType, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellOutput, IOrderedMimeType, mimeTypeIsMergeable, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; let handle = 0; @@ -34,8 +34,8 @@ export class CellOutputViewModel extends Disposable implements ICellOutputViewMo } supportAppend() { - // if there is any mime type other than `application/x.notebook.stream`, then it's not mergeable. - return !this._outputRawData.outputs.find(op => op.mime !== 'application/x.notebook.stream'); + // if there is any mime type that's not mergeable then the whole output is not mergeable. + return this._outputRawData.outputs.every(op => mimeTypeIsMergeable(op.mime)); } resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number] { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts index 9d2d5e1d98a..587cd6ecce4 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection.ts @@ -32,7 +32,7 @@ export class NotebookCellSelectionCollection extends Disposable { private _primary: ICellRange | null = null; - private _selections: ICellRange[] = [{ start: 0, end: 0 }]; + private _selections: ICellRange[] = []; get selections(): ICellRange[] { return this._selections; @@ -42,36 +42,17 @@ export class NotebookCellSelectionCollection extends Disposable { return this._selections[0]; } + get focus(): ICellRange { + return this._primary ?? { start: 0, end: 0 }; + } + setState(primary: ICellRange | null, selections: ICellRange[], forceEventEmit: boolean, source: 'view' | 'model') { - if (primary !== null) { - const primaryRange = primary; - // TODO@rebornix deal with overlap - const newSelections = [primaryRange, ...selections.filter(selection => !(selection.start === primaryRange.start && selection.end === primaryRange.end)).sort((a, b) => a.start - b.start)]; + const changed = primary !== this._primary || !rangesEqual(this._selections, selections); - const changed = primary !== this._primary || !rangesEqual(this._selections, newSelections); - this._primary = primary; - this._selections = newSelections; - - if (!this._selections.length) { - this._selections.push({ start: 0, end: 0 }); - } - - if (changed || forceEventEmit) { - this._onDidChangeSelection.fire(source); - } - } else { - const changed = primary !== this._primary || !rangesEqual(this._selections, selections); - - this._primary = primary; - this._selections = selections; - - if (!this._selections.length) { - this._selections.push({ start: 0, end: 0 }); - } - - if (changed || forceEventEmit) { - this._onDidChangeSelection.fire(source); - } + this._primary = primary; + this._selections = selections; + if (changed || forceEventEmit) { + this._onDidChangeSelection.fire(source); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 491314b43f4..9ae95f4fba8 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -24,7 +24,7 @@ import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbe import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IOutputDto, SelectionStateType, ISelectionState, cellIndexesToRanges, cellRangesToIndexes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IOutputDto, SelectionStateType, ISelectionState, cellIndexesToRanges, cellRangesToIndexes, reduceRanges } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; @@ -353,8 +353,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); } - getSelection() { - return this._selectionCollection.selection; + getFocus() { + return this._selectionCollection.focus; } getSelections() { @@ -396,13 +396,13 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD const selections = cellIndexesToRanges(state.selections.map(sel => this.getCellIndexByHandle(sel))) .map(range => this.validateRange(range)) .filter(range => range !== null) as ICellRange[]; - this._selectionCollection.setState(primarySelection, selections, true, source); + this._selectionCollection.setState(primarySelection, reduceRanges(selections), true, source); } else { - const primarySelection = this.validateRange(state.selections[0]); + const primarySelection = this.validateRange(state.focus); const selections = state.selections .map(range => this.validateRange(range)) .filter(range => range !== null) as ICellRange[]; - this._selectionCollection.setState(primarySelection, selections, true, source); + this._selectionCollection.setState(primarySelection, reduceRanges(selections), true, source); } } } @@ -715,14 +715,14 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } deleteCell(index: number, synchronous: boolean, pushUndoStop: boolean = true) { - const primarySelectionIndex = this.getSelection()?.start ?? null; + const focusSelectionIndex = this.getFocus()?.start ?? null; let endPrimarySelection: number | null = null; - if (index === primarySelectionIndex) { - if (primarySelectionIndex < this.length - 1) { - endPrimarySelection = this._viewCells[primarySelectionIndex + 1].handle; - } else if (primarySelectionIndex === this.length - 1 && this.length > 1) { - endPrimarySelection = this._viewCells[primarySelectionIndex - 1].handle; + if (index === focusSelectionIndex) { + if (focusSelectionIndex < this.length - 1) { + endPrimarySelection = this._viewCells[focusSelectionIndex + 1].handle; + } else if (focusSelectionIndex === this.length - 1 && this.length > 1) { + endPrimarySelection = this._viewCells[focusSelectionIndex - 1].handle; } } @@ -736,7 +736,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD cells: [] }], synchronous, - { kind: SelectionStateType.Index, selections: this.getSelections() }, + { kind: SelectionStateType.Index, focus: this.getFocus(), selections: this.getSelections() }, () => ({ kind: SelectionStateType.Handle, primary: endPrimarySelection, selections: endSelections }), undefined, pushUndoStop @@ -764,7 +764,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD length, newIdx } - ], synchronous, { kind: SelectionStateType.Index, selections: this.getSelections() }, () => ({ kind: SelectionStateType.Index, selections: [{ start: newIdx, end: newIdx + 1 }] }), undefined); + ], synchronous, { kind: SelectionStateType.Index, focus: this.getFocus(), selections: this.getSelections() }, () => ({ kind: SelectionStateType.Index, focus: { start: newIdx, end: newIdx + 1 }, selections: [{ start: newIdx, end: newIdx + 1 }] }), undefined); return true; } @@ -878,106 +878,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return null; } - async joinNotebookCells(index: number, direction: 'above' | 'below', constraint?: CellKind): Promise<{ cell: ICellViewModel, deletedCells: ICellViewModel[] } | null> { - const cell = this.viewCells[index] as CellViewModel; - - if (!this.metadata.editable) { - return null; - } - - if (!cell.getEvaluatedMetadata(this.notebookDocument.metadata).editable) { - return null; - } - - if (constraint && cell.cellKind !== constraint) { - return null; - } - - if (index === 0 && direction === 'above') { - return null; - } - - if (index === this.length - 1 && direction === 'below') { - return null; - } - - if (direction === 'above') { - const above = this.viewCells[index - 1] as CellViewModel; - if (constraint && above.cellKind !== constraint) { - return null; - } - - if (!above.getEvaluatedMetadata(this.notebookDocument.metadata).editable) { - return null; - } - - await above.resolveTextModel(); - if (!above.hasModel()) { - return null; - } - - const endSelections = [above.handle]; - const insertContent = (cell.textModel?.getEOL() ?? '') + cell.getText(); - const aboveCellLineCount = above.textModel.getLineCount(); - const aboveCellLastLineEndColumn = above.textModel.getLineLength(aboveCellLineCount); - - await this._bulkEditService.apply( - [ - new ResourceTextEdit(above.uri, { range: new Range(aboveCellLineCount, aboveCellLastLineEndColumn + 1, aboveCellLineCount, aboveCellLastLineEndColumn + 1), text: insertContent }), - new ResourceNotebookCellEdit(this._notebook.uri, - { - editType: CellEditType.Replace, - index: index, - count: 1, - cells: [] - } - ) - ], - { quotableLabel: 'Join Notebook Cells' } - ); - - this.selectionHandles = endSelections; - - return { cell: above, deletedCells: [cell] }; - } else { - const below = this.viewCells[index + 1] as CellViewModel; - if (constraint && below.cellKind !== constraint) { - return null; - } - - if (!below.getEvaluatedMetadata(this.notebookDocument.metadata).editable) { - return null; - } - - await cell.resolveTextModel(); - if (!cell.hasModel()) { - return null; - } - - const insertContent = (cell.textModel?.getEOL() ?? '') + below.getText(); - - const cellLineCount = cell.textModel.getLineCount(); - const cellLastLineEndColumn = cell.textModel.getLineLength(cellLineCount); - - await this._bulkEditService.apply( - [ - new ResourceTextEdit(cell.uri, { range: new Range(cellLineCount, cellLastLineEndColumn + 1, cellLineCount, cellLastLineEndColumn + 1), text: insertContent }), - new ResourceNotebookCellEdit(this._notebook.uri, - { - editType: CellEditType.Replace, - index: index + 1, - count: 1, - cells: [] - } - ) - ], - { quotableLabel: 'Join Notebook Cells' } - ); - - return { cell, deletedCells: [below] }; - } - } - getEditorViewState(): INotebookEditorViewState { const editingCells: { [key: number]: boolean } = {}; this._viewCells.forEach((cell, i) => { diff --git a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts index 52d2a01a081..5bfc431dc34 100644 --- a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts @@ -14,6 +14,7 @@ import { ISelectionState, NotebookCellMetadata } from 'vs/workbench/contrib/note export interface ITextCellEditingDelegate { insertCell?(index: number, cell: NotebookCellTextModel, endSelections?: ISelectionState): void; deleteCell?(index: number, endSelections?: ISelectionState): void; + replaceCell?(index: number, count: number, cells: NotebookCellTextModel[], endSelections?: ISelectionState): void; moveCell?(fromIndex: number, length: number, toIndex: number, beforeSelections: ISelectionState | undefined, endSelections?: ISelectionState): void; updateCellMetadata?(index: number, newMetadata: NotebookCellMetadata): void; } @@ -63,34 +64,22 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { } undo(): void { - if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) { - throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo'); + if (!this.editingDelegate.replaceCell) { + throw new Error('Notebook Replace Cell not implemented for Undo/Redo'); } this.diffs.forEach(diff => { - for (let i = 0; i < diff[2].length; i++) { - this.editingDelegate.deleteCell!(diff[0], this.beforeHandles); - } - - diff[1].reverse().forEach(cell => { - this.editingDelegate.insertCell!(diff[0], cell, this.beforeHandles); - }); + this.editingDelegate.replaceCell!(diff[0], diff[2].length, diff[1], this.beforeHandles); }); } redo(): void { - if (!this.editingDelegate.deleteCell || !this.editingDelegate.insertCell) { - throw new Error('Notebook Insert/Delete Cell not implemented for Undo/Redo'); + if (!this.editingDelegate.replaceCell) { + throw new Error('Notebook Replace Cell not implemented for Undo/Redo'); } this.diffs.reverse().forEach(diff => { - for (let i = 0; i < diff[1].length; i++) { - this.editingDelegate.deleteCell!(diff[0], this.endHandles); - } - - diff[2].reverse().forEach(cell => { - this.editingDelegate.insertCell!(diff[0], cell, this.endHandles); - }); + this.editingDelegate.replaceCell!(diff[0], diff[1].length, diff[2], this.endHandles); }); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 08eb5a6a5c4..562043609ea 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -7,6 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ICell, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata, NotebookDocumentMetadata, TransientOptions, IOutputDto, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { URI } from 'vs/base/common/uri'; +import * as UUID from 'vs/base/common/uuid'; import * as model from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -179,3 +180,27 @@ export class NotebookCellTextModel extends Disposable implements ICell { super.dispose(); } } + +export function cloneMetadata(cell: NotebookCellTextModel) { + return { + editable: cell.metadata?.editable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + }; +} + +export function cloneNotebookCellTextModel(cell: NotebookCellTextModel) { + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs.map(output => ({ + outputs: output.outputs, + /* paste should generate new outputId */ outputId: UUID.generateUuid() + })), + metadata: cloneMetadata(cell) + }; +} diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 0246e87346e..c48363ef2ed 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -219,6 +219,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _operationManager: NotebookOperationManager; private _eventEmitter: DelayedEmitter; + get length() { + return this._cells.length; + } + get cells(): readonly NotebookCellTextModel[] { return this._cells; } @@ -414,6 +418,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, { insertCell: (index, cell, endSelections) => { this._insertNewCell(index, [cell], true, endSelections); }, deleteCell: (index, endSelections) => { this._removeCell(index, 1, true, endSelections); }, + replaceCell: (index, count, cells, endSelections) => { this._replaceNewCells(index, count, cells, true, endSelections); }, }, undefined, undefined), undefined, undefined); } @@ -505,6 +510,27 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._eventEmitter.emit({ kind: NotebookCellsChangeType.ModelChange, changes: [[index, count, []]], transient: false }, synchronous, endSelections); } + private _replaceNewCells(index: number, count: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections: ISelectionState | undefined) { + for (let i = index; i < index + count; i++) { + const cell = this._cells[i]; + this._cellListeners.get(cell.handle)?.dispose(); + this._cellListeners.delete(cell.handle); + } + + for (let i = 0; i < cells.length; i++) { + this._mapping.set(cells[i].handle, cells[i]); + const dirtyStateListener = cells[i].onDidChangeContent(() => { + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellContent, transient: false }, true); + }); + + this._cellListeners.set(cells[i].handle, dirtyStateListener); + } + + this._cells.splice(index, count, ...cells); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ModelChange, changes: [[index, count, cells]], transient: false }, synchronous, endSelections); + + } + private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) { const keys = new Set([...Object.keys(a || {}), ...Object.keys(b || {})]); for (let key of keys) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 7c8e99762d1..9baeed7cc7b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -305,10 +305,7 @@ export interface ISelectionHandleState { export interface ISelectionIndexState { kind: SelectionStateType.Index; - - /** - * [primarySelection, ...secondarySelections] - */ + focus: ICellRange; selections: ICellRange[]; } @@ -466,6 +463,7 @@ export namespace CellUri { type MimeTypeInfo = { alwaysSecure?: boolean; supportedByCore?: boolean; + mergeable?: boolean; }; const _mimeTypeInfo = new Map([ @@ -479,6 +477,9 @@ const _mimeTypeInfo = new Map([ ['image/jpeg', { supportedByCore: true }], ['text/x-javascript', { supportedByCore: true }], ['application/x.notebook.error-traceback', { alwaysSecure: true, supportedByCore: true }], + ['application/x.notebook.stream', { alwaysSecure: true, supportedByCore: true, mergeable: true }], + ['application/x.notebook.stdout', { alwaysSecure: true, supportedByCore: true, mergeable: true }], + ['application/x.notebook.stderr', { alwaysSecure: true, supportedByCore: true, mergeable: true }], ]); export function mimeTypeIsAlwaysSecure(mimeType: string): boolean { @@ -489,6 +490,10 @@ export function mimeTypeSupportedByCore(mimeType: string) { return _mimeTypeInfo.get(mimeType)?.supportedByCore ?? false; } +export function mimeTypeIsMergeable(mimeType: string): boolean { + return _mimeTypeInfo.get(mimeType)?.mergeable ?? false; +} + // if (isWindows) { // value = value.replace(/\//g, '\\'); // } @@ -792,6 +797,7 @@ export interface INotebookDecorationRenderOptions { export function cellIndexesToRanges(indexes: number[]) { + indexes.sort((a, b) => a - b); const first = indexes.shift(); if (first === undefined) { @@ -819,3 +825,36 @@ export function cellRangesToIndexes(ranges: ICellRange[]) { return indexes; } + +/** + * todo@rebornix notebookBrowser.reduceCellRanges + * @returns + */ +export function reduceRanges(ranges: ICellRange[]) { + const sorted = ranges.sort((a, b) => a.start - b.start); + const first = sorted[0]; + + if (!first) { + return []; + } + + return sorted.reduce((prev: ICellRange[], curr) => { + const last = prev[prev.length - 1]; + if (last.end >= curr.start) { + last.end = Math.max(last.end, curr.end); + } else { + prev.push(curr); + } + return prev; + }, [first] as ICellRange[]); +} + +/** + * todo@rebornix test and sort + * @param range + * @param other + * @returns + */ +export function cellRangeContains(range: ICellRange, other: ICellRange): boolean { + return other.start >= range.start && other.end <= range.end; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 7eef151b82a..294e6611938 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -71,8 +71,8 @@ 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... + if (this.isDirty() || !this.isResolved() || this._saveSequentializer.hasPending()) { + // skip when dirty, unresolved, or when saving return; } if (!e.affects(this.resource, FileChangeType.UPDATED)) { @@ -81,6 +81,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } const stats = await this._resolveStats(this.resource); if (stats && this._lastResolvedFileStat && stats.etag !== this._lastResolvedFileStat.etag) { + this._logService.debug('[notebook editor model] trigger load after file event'); this.load({ forceReadFromDisk: true }); } })); @@ -151,6 +152,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { + this._logService.debug('[notebook editor model] load from provider (forceRead)', this.resource.toString()); this._loadFromProvider(undefined); assertType(this.isResolved()); return this; @@ -166,6 +168,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM return this; // Make sure meanwhile someone else did not succeed in loading } + this._logService.debug('[notebook editor model] load from provider', this.resource.toString()); await this._loadFromProvider(backup?.meta?.backupId); assertType(this.isResolved()); return this; @@ -193,6 +196,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } if (!this.notebook) { + this._logService.debug('[notebook editor model] loading NEW notebook', this.resource.toString()); // FRESH there is no notebook yet and we are now creating it // UGLY @@ -235,6 +239,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } else { // UPDATE exitsing notebook with data that we have just fetched + this._logService.debug('[notebook editor model] loading onto EXISTING notebook', this.resource.toString()); 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 }]; diff --git a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts index bc9f5874744..5abb1ffffd9 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookCommon.test.ts @@ -8,6 +8,7 @@ import { NOTEBOOK_DISPLAY_ORDER, sortMimeTypes, CellKind, diff, CellUri, cellRan import { TestCell, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { reduceCellRanges } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; suite('NotebookCommon', () => { const instantiationService = setupInstantiationService(); @@ -313,5 +314,19 @@ suite('CellRange', function () { assert.deepStrictEqual(cellIndexesToRanges([0, 1]), [{ start: 0, end: 2 }]); assert.deepStrictEqual(cellIndexesToRanges([0, 1, 2]), [{ start: 0, end: 3 }]); assert.deepStrictEqual(cellIndexesToRanges([0, 1, 3]), [{ start: 0, end: 2 }, { start: 3, end: 4 }]); + + assert.deepStrictEqual(cellIndexesToRanges([1, 0]), [{ start: 0, end: 2 }]); + assert.deepStrictEqual(cellIndexesToRanges([1, 2, 0]), [{ start: 0, end: 3 }]); + assert.deepStrictEqual(cellIndexesToRanges([3, 1, 0]), [{ start: 0, end: 2 }, { start: 3, end: 4 }]); + + assert.deepStrictEqual(cellIndexesToRanges([9, 10]), [{ start: 9, end: 11 }]); + assert.deepStrictEqual(cellIndexesToRanges([10, 9]), [{ start: 9, end: 11 }]); + }); + + test('Reduce ranges', function () { + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 1 }, { start: 1, end: 2 }]), [{ start: 0, end: 2 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 2 }, { start: 1, end: 3 }]), [{ start: 0, end: 3 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 1, end: 3 }, { start: 0, end: 2 }]), [{ start: 0, end: 3 }]); + assert.deepStrictEqual(reduceCellRanges([{ start: 0, end: 2 }, { start: 4, end: 5 }]), [{ start: 0, end: 2 }, { start: 4, end: 5 }]); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditor.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditor.test.ts new file mode 100644 index 00000000000..ce65216a184 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/test/notebookEditor.test.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { mock } from 'vs/base/test/common/mock'; +import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; +import { expandCellRangesWithHiddenCells, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; + +suite('ListViewInfoAccessor', () => { + const instantiationService = setupInstantiationService(); + + test('basics', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3;', 'javascript', CellKind.Code, [], {}] + ], + (editor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + const listViewInfoAccessor = new ListViewInfoAccessor(cellList); + + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[0]), 0); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[1]), 1); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[2]), 2); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[3]), 3); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[4]), 4); + assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(0, 1), { start: 0, end: 1 }); + assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(1, 2), { start: 1, end: 2 }); + + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 2, true); + viewModel.updateFoldingRanges(foldingModel.regions); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[0]), 0); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[1]), -1); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[2]), 1); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[3]), -1); + assert.strictEqual(listViewInfoAccessor.getViewIndex(viewModel.viewCells[4]), -1); + + assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(0, 1), { start: 0, end: 2 }); + assert.deepStrictEqual(listViewInfoAccessor.getCellRangeFromViewRange(1, 2), { start: 2, end: 5 }); + assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(0, 1), viewModel.viewCells.slice(0, 2)); + assert.deepStrictEqual(listViewInfoAccessor.getCellsFromViewRange(1, 2), viewModel.viewCells.slice(2)); + + const notebookEditor = new class extends mock() { + getViewIndex(cell: ICellViewModel) { return listViewInfoAccessor.getViewIndex(cell); } + getCellRangeFromViewRange(startIndex: number, endIndex: number) { return listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex); } + }; + + assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, viewModel, [{ start: 0, end: 1 }]), [{ start: 0, end: 2 }]); + assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, viewModel, [{ start: 2, end: 3 }]), [{ start: 2, end: 5 }]); + assert.deepStrictEqual(expandCellRangesWithHiddenCells(notebookEditor, viewModel, [{ start: 0, end: 1 }, { start: 2, end: 3 }]), [{ start: 0, end: 5 }]); + }); + }); +}); diff --git a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts index 5e33080f8a5..9a25a984e5c 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookEditorKernelManager.test.ts @@ -31,7 +31,7 @@ suite('NotebookEditorKernelManager', () => { const loadKernelPreloads = async () => { }; async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise) { - return _withTestNotebook(instantiationService, cells, (_editor, viewModel, textModel) => callback(viewModel, textModel)); + return _withTestNotebook(cells, (editor) => callback(editor.viewModel, editor.viewModel.notebookDocument)); } test('ctor', () => { diff --git a/src/vs/workbench/contrib/notebook/test/notebookSelection.test.ts b/src/vs/workbench/contrib/notebook/test/notebookSelection.test.ts index 4e77f4b4ad9..18b9fcb98ab 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookSelection.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookSelection.test.ts @@ -4,88 +4,95 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FoldingModel, updateFoldingStateAtIndex } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection'; -import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { CellEditType, CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { createNotebookCellList, setupInstantiationService, TestCell, withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; suite('NotebookSelection', () => { - test('selection is never empty', function () { + test('focus is never empty', function () { const selectionCollection = new NotebookCellSelectionCollection(); - assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]); + assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 }); selectionCollection.setState(null, [], true, 'model'); - assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 0 }]); - }); - - test('selections[0] is primary selection', function () { - const selectionCollection = new NotebookCellSelectionCollection(); - selectionCollection.setState(null, [{ start: 0, end: 1 }, { start: 3, end: 5 }], true, 'model'); - assert.deepStrictEqual(selectionCollection.selection, { start: 0, end: 1 }); - assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 1 }, { start: 3, end: 5 }]); - - selectionCollection.setState({ start: 0, end: 1 }, [{ start: 3, end: 5 }], true, 'model'); - assert.deepStrictEqual(selectionCollection.selection, { start: 0, end: 1 }); - assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 1 }, { start: 3, end: 5 }]); - - selectionCollection.setState({ start: 0, end: 1 }, [], true, 'model'); - assert.deepStrictEqual(selectionCollection.selection, { start: 0, end: 1 }); - assert.deepStrictEqual(selectionCollection.selections, [{ start: 0, end: 1 }]); - + assert.deepStrictEqual(selectionCollection.focus, { start: 0, end: 0 }); }); }); suite('NotebookCellList focus/selection', () => { const instantiationService = setupInstantiationService(); + const textModelService = instantiationService.get(ITextModelService); - test('notebook cell list setFocus', function () { - withTestNotebook( - instantiationService, + test('notebook cell list setFocus', async function () { + await withTestNotebook( [ ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const cellList = createNotebookCellList(instantiationService); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); cellList.setFocus([0]); - assert.deepStrictEqual(viewModel.getSelection(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); cellList.setFocus([1]); - assert.deepStrictEqual(viewModel.getSelection(), { start: 1, end: 2 }); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); cellList.detachViewModel(); }); }); - test('notebook cell list setSelections', function () { - withTestNotebook( - instantiationService, + test('notebook cell list setSelections', async function () { + await withTestNotebook( [ ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const cellList = createNotebookCellList(instantiationService); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 2); cellList.setSelection([0]); // the only selection is also the focus - assert.deepStrictEqual(viewModel.getSelection(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); // set selection does not modify focus cellList.setSelection([1]); - assert.deepStrictEqual(viewModel.getSelection(), { start: 0, end: 1 }); - // `getSelections()` now returns all focus/selection ranges - assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }, { start: 1, end: 2 }]); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); }); }); - test('notebook cell list focus/selection with folding regions', function () { - withTestNotebook( - instantiationService, + test('notebook cell list setFocus', async function () { + await withTestNotebook( + [ + ['var a = 1;', 'javascript', CellKind.Code, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}] + ], + (editor) => { + const viewModel = editor.viewModel; + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + + assert.strictEqual(cellList.length, 2); + cellList.setFocus([0]); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + + cellList.setFocus([1]); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + + cellList.setSelection([1]); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); + }); + }); + + + test('notebook cell list focus/selection from UI', async function () { + await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markdown, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}], @@ -93,13 +100,52 @@ suite('NotebookCellList focus/selection', () => { ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markdown, [], {}] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); + + // arrow down, move both focus and selections + cellList.setFocus([1], new KeyboardEvent('keydown'), undefined); + cellList.setSelection([1], new KeyboardEvent('keydown'), undefined); + assert.deepStrictEqual(viewModel.getFocus(), { start: 1, end: 2 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); + + // shift+arrow down, expands selection + cellList.setFocus([2], new KeyboardEvent('keydown'), undefined); + cellList.setSelection([1, 2]); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]); + + // arrow down, will move focus but not expand selection + cellList.setFocus([3], new KeyboardEvent('keydown'), undefined); + assert.deepStrictEqual(viewModel.getFocus(), { start: 3, end: 4 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 3 }]); + }); + }); + + + test('notebook cell list focus/selection with folding regions', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markdown, [], {}] + ], + (editor) => { + const viewModel = editor.viewModel; const foldingModel = new FoldingModel(); foldingModel.attachViewModel(viewModel); const cellList = createNotebookCellList(instantiationService); cellList.attachViewModel(viewModel); assert.strictEqual(cellList.length, 5); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); cellList.setFocus([0]); updateFoldingStateAtIndex(foldingModel, 0, true); @@ -108,31 +154,116 @@ suite('NotebookCellList focus/selection', () => { cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); assert.strictEqual(cellList.length, 3); - // currently, focus on a folded cell will only select the cell itself, excluding its "inner" cells - assert.deepStrictEqual(viewModel.getSelection(), { start: 0, end: 1 }); + // currently, focus on a folded cell will only focus the cell itself, excluding its "inner" cells + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); cellList.focusNext(1, false); // focus next should skip the folded items - assert.deepStrictEqual(viewModel.getSelection(), { start: 2, end: 3 }); - assert.deepStrictEqual(viewModel.getSelections(), [{ start: 2, end: 3 }]); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); + assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); // unfold updateFoldingStateAtIndex(foldingModel, 2, false); viewModel.updateFoldingRanges(foldingModel.regions); cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); assert.strictEqual(cellList.length, 4); - assert.deepStrictEqual(viewModel.getSelection(), { start: 2, end: 3 }); + assert.deepStrictEqual(viewModel.getFocus(), { start: 2, end: 3 }); }); }); - test('notebook validate range', () => { - withTestNotebook( - instantiationService, + test('notebook cell list focus/selection with folding regions and applyEdits', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['var c = 3', 'javascript', CellKind.Markdown, [], {}], + ['# header d', 'markdown', CellKind.Markdown, [], {}], + ['var e = 4;', 'javascript', CellKind.Code, [], {}], + ], + (editor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + cellList.setFocus([0]); + cellList.setSelection([0]); + + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 2, true); + viewModel.updateFoldingRanges(foldingModel.regions); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + assert.strictEqual(cellList.getModelIndex2(0), 0); + assert.strictEqual(cellList.getModelIndex2(1), 2); + + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 2, cells: [] + }], true, undefined, () => undefined, undefined, false); + viewModel.updateFoldingRanges(foldingModel.regions); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + + assert.strictEqual(cellList.getModelIndex2(0), 0); + assert.strictEqual(cellList.getModelIndex2(1), 3); + + // mimic undo + viewModel.notebookDocument.applyEdits([{ + editType: CellEditType.Replace, index: 0, count: 0, cells: [ + new TestCell(viewModel.viewType, 7, '# header f', 'markdown', CellKind.Code, [], textModelService), + new TestCell(viewModel.viewType, 8, 'var g = 5;', 'javascript', CellKind.Code, [], textModelService) + ] + }], true, undefined, () => undefined, undefined, false); + viewModel.updateFoldingRanges(foldingModel.regions); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + assert.strictEqual(cellList.getModelIndex2(0), 0); + assert.strictEqual(cellList.getModelIndex2(1), 1); + assert.strictEqual(cellList.getModelIndex2(2), 2); + + + }); + }); + + test('notebook cell list getModelIndex', async function () { + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markdown, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markdown, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markdown, [], {}] + ], + (editor) => { + const viewModel = editor.viewModel; + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + + updateFoldingStateAtIndex(foldingModel, 0, true); + updateFoldingStateAtIndex(foldingModel, 2, true); + viewModel.updateFoldingRanges(foldingModel.regions); + cellList.setHiddenAreas(viewModel.getHiddenRanges(), true); + + assert.deepStrictEqual(cellList.getModelIndex2(-1), 0); + assert.deepStrictEqual(cellList.getModelIndex2(0), 0); + assert.deepStrictEqual(cellList.getModelIndex2(1), 2); + assert.deepStrictEqual(cellList.getModelIndex2(2), 4); + }); + }); + + + test('notebook validate range', async () => { + await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markdown, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; assert.deepStrictEqual(viewModel.validateRange(null), null); assert.deepStrictEqual(viewModel.validateRange(undefined), null); assert.deepStrictEqual(viewModel.validateRange({ start: 0, end: 0 }), null); @@ -145,30 +276,31 @@ suite('NotebookCellList focus/selection', () => { }); }); - test('notebook updateSelectionState', function () { - withTestNotebook( - instantiationService, + test('notebook updateSelectionState', async function () { + await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markdown, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - viewModel.updateSelectionsState({ kind: SelectionStateType.Index, selections: [{ start: 1, end: 2 }, { start: -1, end: 0 }] }); + (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }, { start: -1, end: 0 }] }); assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]); }); }); - test('notebook cell selection w/ cell deletion', function () { - withTestNotebook( - instantiationService, + test('notebook cell selection w/ cell deletion', async function () { + await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markdown, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}] ], - (editor, viewModel) => { - viewModel.updateSelectionsState({ kind: SelectionStateType.Index, selections: [{ start: 1, end: 2 }] }); + (editor) => { + const viewModel = editor.viewModel; + viewModel.updateSelectionsState({ kind: SelectionStateType.Index, focus: { start: 1, end: 2 }, selections: [{ start: 1, end: 2 }] }); viewModel.deleteCell(1, true, false); - assert.deepStrictEqual(viewModel.getSelections(), [{ start: 0, end: 1 }]); + assert.deepStrictEqual(viewModel.getFocus(), { start: 0, end: 1 }); + assert.deepStrictEqual(viewModel.getSelections(), []); }); }); }); diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 285c567a834..1f9790c7cb0 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -16,14 +16,15 @@ suite('NotebookTextModel', () => { test('insert', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(viewModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], textModelService)] }, @@ -39,14 +40,15 @@ suite('NotebookTextModel', () => { test('multiple inserts at same position', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], textModelService)] }, @@ -62,14 +64,14 @@ suite('NotebookTextModel', () => { test('delete', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 3, count: 1, cells: [] }, @@ -83,14 +85,15 @@ suite('NotebookTextModel', () => { test('delete + insert', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, @@ -106,14 +109,15 @@ suite('NotebookTextModel', () => { test('delete + insert at same position', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, @@ -129,14 +133,15 @@ suite('NotebookTextModel', () => { test('(replace) delete + insert at same position', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, ], true, undefined, () => undefined, undefined); @@ -151,11 +156,11 @@ suite('NotebookTextModel', () => { test('output', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ], - (editor, viewModel, textModel) => { + (editor) => { + const textModel = editor.viewModel.notebookDocument; // invalid index 1 assert.throws(() => { @@ -224,11 +229,11 @@ suite('NotebookTextModel', () => { test('metadata', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ], - (editor, viewModel, textModel) => { + (editor) => { + const textModel = editor.viewModel.notebookDocument; // invalid index 1 assert.throws(() => { @@ -262,14 +267,15 @@ suite('NotebookTextModel', () => { test('multiple inserts in one edit', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const viewModel = editor.viewModel; + const textModel = editor.viewModel.notebookDocument; let changeEvent: NotebookTextModelChangedEvent | undefined = undefined; const eventListener = textModel.onDidChangeContent(e => { changeEvent = e; @@ -279,7 +285,7 @@ suite('NotebookTextModel', () => { textModel.applyEdits([ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true, undefined, () => ({ kind: SelectionStateType.Index, selections: [{ start: 0, end: 1 }] }), undefined); + ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined); assert.equal(textModel.cells.length, 4); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); @@ -297,14 +303,14 @@ suite('NotebookTextModel', () => { test('insert and metadata change in one edit', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel, textModel) => { + (editor) => { + const textModel = editor.viewModel.notebookDocument; let changeEvent: NotebookTextModelChangedEvent | undefined = undefined; const eventListener = textModel.onDidChangeContent(e => { changeEvent = e; @@ -318,7 +324,7 @@ suite('NotebookTextModel', () => { editType: CellEditType.Metadata, metadata: { editable: false }, } - ], true, undefined, () => ({ kind: SelectionStateType.Index, selections: [{ start: 0, end: 1 }] }), undefined); + ], true, undefined, () => ({ kind: SelectionStateType.Index, focus: { start: 0, end: 1 }, selections: [{ start: 0, end: 1 }] }), undefined); assert.notEqual(changeEvent, undefined); assert.equal(changeEvent!.rawEvents.length, 2); @@ -331,9 +337,10 @@ suite('NotebookTextModel', () => { test('Updating appending/updating output in Notebooks does not work as expected #117273', function () { - withTestNotebook(instantiationService, [ + withTestNotebook([ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }] - ], (_editor, _view, model) => { + ], (editor) => { + const model = editor.viewModel.notebookDocument; assert.strictEqual(model.cells.length, 1); assert.strictEqual(model.cells[0].outputs.length, 0); diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 1bd2891e978..9de4cd5238e 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -32,12 +32,12 @@ suite('NotebookViewModel', () => { test('insert/delete', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; assert.equal(viewModel.viewCells[0].metadata?.editable, true); assert.equal(viewModel.viewCells[1].metadata?.editable, false); @@ -56,13 +56,13 @@ suite('NotebookViewModel', () => { test('move cells down', async function () { await withTestNotebook( - instantiationService, [ ['//a', 'javascript', CellKind.Code, [], { editable: true }], ['//b', 'javascript', CellKind.Code, [], { editable: true }], ['//c', 'javascript', CellKind.Code, [], { editable: true }], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; viewModel.moveCellToIdx(0, 1, 0, true); // no-op assert.equal(viewModel.viewCells[0].getText(), '//a'); @@ -85,13 +85,13 @@ suite('NotebookViewModel', () => { test('move cells up', async function () { await withTestNotebook( - instantiationService, [ ['//a', 'javascript', CellKind.Code, [], { editable: true }], ['//b', 'javascript', CellKind.Code, [], { editable: true }], ['//c', 'javascript', CellKind.Code, [], { editable: true }], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; viewModel.moveCellToIdx(1, 1, 0, true); // b, a, c assert.equal(viewModel.viewCells[0].getText(), '//b'); @@ -108,12 +108,12 @@ suite('NotebookViewModel', () => { test('index', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }] ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const firstViewCell = viewModel.viewCells[0]; const lastViewCell = viewModel.viewCells[viewModel.viewCells.length - 1]; @@ -135,7 +135,6 @@ suite('NotebookViewModel', () => { test('metadata', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }], @@ -143,7 +142,8 @@ suite('NotebookViewModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }], ['var e = 5;', 'javascript', CellKind.Code, [], { editable: false }], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; viewModel.notebookDocument.metadata = { editable: true, cellEditable: true, cellHasExecutionOrder: true, trusted: true }; const defaults = { hasExecutionOrder: true }; @@ -237,11 +237,8 @@ function getVisibleCells(cells: T[], hiddenRanges: ICellRange[]) { } suite('NotebookViewModel Decorations', () => { - const instantiationService = setupInstantiationService(); - test('tracking range', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }], @@ -249,7 +246,8 @@ suite('NotebookViewModel Decorations', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }], ['var e = 5;', 'javascript', CellKind.Code, [], { editable: false }], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 2 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, @@ -297,7 +295,6 @@ suite('NotebookViewModel Decorations', () => { test('tracking range 2', async function () { await withTestNotebook( - instantiationService, [ ['var a = 1;', 'javascript', CellKind.Code, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], { editable: true }], @@ -307,7 +304,8 @@ suite('NotebookViewModel Decorations', () => { ['var e = 6;', 'javascript', CellKind.Code, [], { editable: false }], ['var e = 7;', 'javascript', CellKind.Code, [], { editable: false }], ], - (editor, viewModel) => { + (editor) => { + const viewModel = editor.viewModel; const trackedId = viewModel.setTrackedRange('test', { start: 1, end: 3 }, TrackedRangeStickiness.GrowsOnlyWhenTypingAfter); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 277224659e1..f8e20a4a655 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -4,17 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { NotImplementedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { Range } from 'vs/editor/common/core/range'; -import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -22,14 +16,12 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { IListService, ListService } from 'vs/platform/list/browser/listService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { EditorModel } from 'vs/workbench/common/editor'; -import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, INotebookDeltaDecoration, INotebookEditorCreationOptions, NotebookEditorOptions, ICellOutputViewModel, IInsetRenderOutput, ICommonCellInfo, IGenericCellViewModel, INotebookCellOutputLayoutInfo, CellEditState, IActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; +import { ICellViewModel, IActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; -import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { 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, ICellRange, INotebookEditorModel, INotebookKernel, IOutputDto, IResolvedNotebookEditorModel, NotebookCellMetadata, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; +import { CellKind, CellUri, ICellRange, INotebookEditorModel, IOutputDto, IResolvedNotebookEditorModel, NotebookCellMetadata, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -39,8 +31,10 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; +import { ListViewInfoAccessor } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; +import { mock } from 'vs/base/test/common/mock'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export class TestCell extends NotebookCellTextModel { constructor( @@ -56,317 +50,6 @@ export class TestCell extends NotebookCellTextModel { } } -export class TestNotebookEditor implements INotebookEditor { - isEmbedded = false; - private _isDisposed = false; - - get isDisposed() { - return this._isDisposed; - } - - creationOptions: INotebookEditorCreationOptions = { isEmbedded: false }; - - constructor(readonly viewModel: NotebookViewModel) { } - getVisibleRangesPlusViewportAboveBelow(): ICellRange[] { - throw new Error('Method not implemented.'); - } - getSelection(): ICellRange | undefined { - throw new Error('Method not implemented.'); - } - getSelections(): ICellRange[] { - throw new Error('Method not implemented.'); - } - getSelectionViewModels(): ICellViewModel[] { - throw new Error('Method not implemented.'); - } - revealCellRangeInView(range: ICellRange): void { - throw new Error('Method not implemented.'); - } - revealInViewAtTop(cell: ICellViewModel): void { - throw new Error('Method not implemented.'); - } - getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo { - throw new Error('Method not implemented.'); - } - focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void { - throw new Error('Method not implemented.'); - } - 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.'); - } - - setMarkdownCellEditState(cellId: string, editState: CellEditState): void { - throw new Error('Method not implemented.'); - } - markdownCellDragStart(cellId: string, position: { clientY: number }): void { - throw new Error('Method not implemented.'); - } - markdownCellDrag(cellId: string, position: { clientY: number }): void { - throw new Error('Method not implemented.'); - } - markdownCellDragEnd(cellId: string, position: { clientY: number }): void { - throw new Error('Method not implemented.'); - } - updateMarkdownPreviewSelectionState(cell: ICellViewModel, isSelected: boolean): Promise { - throw new Error('Method not implemented.'); - } - async beginComputeContributedKernels(): Promise { - return []; - } - setEditorDecorations(key: string, range: ICellRange): void { - // throw new Error('Method not implemented.'); - } - removeEditorDecorations(key: string): void { - // throw new Error('Method not implemented.'); - } - setOptions(options: NotebookEditorOptions | undefined): Promise { - throw new Error('Method not implemented.'); - } - - hideInset(output: ICellOutputViewModel): void { - throw new Error('Method not implemented.'); - } - - multipleKernelsAvailable: boolean = false; - onDidScroll: Event = new Emitter().event; - onDidChangeAvailableKernels: Event = new Emitter().event; - onDidChangeActiveCell: Event = new Emitter().event; - onDidChangeVisibleRanges: Event = new Emitter().event; - onDidChangeSelection: Event = new Emitter().event; - visibleRanges: ICellRange[] = []; - textModel?: NotebookTextModel | undefined; - - hasModel(): this is IActiveNotebookEditor { - return true; - } - - onDidFocusEditorWidget: Event = new Emitter().event; - hasFocus(): boolean { - return true; - } - - hasWebviewFocus() { - return false; - } - - hasOutputTextSelection() { - return false; - } - - getId(): string { - return 'notebook.testEditor'; - } - - cursorNavigationMode = false; - activeKernel: INotebookKernel | undefined; - onDidChangeKernel: Event = new Emitter().event; - onDidChangeActiveEditor: Event = new Emitter().event; - activeCodeEditor: IEditor | undefined; - getDomNode(): HTMLElement { - throw new Error('Method not implemented.'); - } - - getOverflowContainerDomNode(): HTMLElement { - throw new Error('Method not implemented.'); - } - - private _onDidChangeModel = new Emitter(); - onDidChangeModel: Event = this._onDidChangeModel.event; - getContribution(id: string): T { - throw new Error('Method not implemented.'); - } - onMouseUp(listener: (e: INotebookEditorMouseEvent) => void): IDisposable { - throw new Error('Method not implemented.'); - } - onMouseDown(listener: (e: INotebookEditorMouseEvent) => void): IDisposable { - throw new Error('Method not implemented.'); - } - - setHiddenAreas(_ranges: ICellRange[]): boolean { - throw new Error('Method not implemented.'); - } - - getInnerWebview(): Webview | undefined { - throw new Error('Method not implemented.'); - } - - cancelNotebookCellExecution(cell: ICellViewModel): void { - throw new Error('Method not implemented.'); - } - - executeNotebook(): Promise { - throw new Error('Method not implemented.'); - } - - cancelNotebookExecution(): void { - throw new Error('Method not implemented.'); - } - - executeNotebookCell(cell: ICellViewModel): Promise { - throw new Error('Method not implemented.'); - } - - postMessage(): void { - throw new Error('Method not implemented.'); - } - - addClassName(className: string): void { - throw new Error('Method not implemented.'); - } - - removeClassName(className: string): void { - throw new Error('Method not implemented.'); - } - - setCellEditorSelection(cell: CellViewModel, selection: Range): void { - throw new Error('Method not implemented.'); - } - - focusElement(cell: CellViewModel): void { - throw new Error('Method not implemented.'); - } - - moveCellDown(cell: CellViewModel): Promise { - throw new Error('Method not implemented.'); - } - - moveCellUp(cell: CellViewModel): Promise { - throw new Error('Method not implemented.'); - } - - async moveCellsToIdx(index: number, length: number, toIdx: number): Promise { - throw new Error('Method not implemented.'); - } - - splitNotebookCell(cell: ICellViewModel): Promise { - throw new Error('Method not implemented.'); - } - - joinNotebookCells(cell: ICellViewModel, direction: 'above' | 'below', constraint?: CellKind): Promise { - throw new Error('Method not implemented.'); - } - - setSelection(cell: CellViewModel, selection: Range): void { - throw new Error('Method not implemented.'); - } - revealRangeInViewAsync(cell: CellViewModel, range: Range): Promise { - throw new Error('Method not implemented.'); - } - revealRangeInCenterAsync(cell: CellViewModel, range: Range): Promise { - throw new Error('Method not implemented.'); - } - revealRangeInCenterIfOutsideViewportAsync(cell: CellViewModel, range: Range): Promise { - throw new Error('Method not implemented.'); - } - - revealLineInViewAsync(cell: CellViewModel, line: number): Promise { - throw new Error('Method not implemented.'); - } - getLayoutInfo(): NotebookLayoutInfo { - throw new Error('Method not implemented.'); - } - revealLineInCenterIfOutsideViewportAsync(cell: CellViewModel, line: number): Promise { - throw new Error('Method not implemented.'); - } - revealLineInCenterAsync(cell: CellViewModel, line: number): Promise { - throw new Error('Method not implemented.'); - } - focus(): void { - throw new Error('Method not implemented.'); - } - showFind(): void { - throw new Error('Method not implemented.'); - } - hideFind(): void { - throw new Error('Method not implemented.'); - } - revealInView(cell: CellViewModel): void { - throw new Error('Method not implemented.'); - } - revealInCenter(cell: CellViewModel): void { - throw new Error('Method not implemented.'); - } - revealInCenterIfOutsideViewport(cell: CellViewModel): void { - throw new Error('Method not implemented.'); - } - insertNotebookCell(cell: CellViewModel, type: CellKind, direction: 'above' | 'below'): CellViewModel { - throw new Error('Method not implemented.'); - } - deleteNotebookCell(cell: CellViewModel): Promise { - throw new Error('Method not implemented.'); - } - focusNotebookCell(cell: CellViewModel, focusItem: 'editor' | 'container' | 'output'): void { - // throw new Error('Method not implemented.'); - } - getActiveCell(): CellViewModel | undefined { - // throw new Error('Method not implemented.'); - return; - } - async layoutNotebookCell(cell: CellViewModel, height: number): Promise { - // throw new Error('Method not implemented.'); - return; - } - createOutput(cell: CellViewModel, output: IInsetRenderOutput, offset: number): Promise { - return Promise.resolve(); - } - createMarkdownPreview(cell: ICellViewModel): Promise { - return Promise.resolve(); - } - async unhideMarkdownPreview(cell: ICellViewModel): Promise { - // noop - } - async hideMarkdownPreview(cell: ICellViewModel): Promise { - // noop - } - removeMarkdownPreview(cell: ICellViewModel): Promise { - return Promise.resolve(); - } - updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean): void { - // noop - } - removeInset(output: ICellOutputViewModel): void { - // throw new Error('Method not implemented.'); - } - triggerScroll(event: IMouseWheelEvent): void { - // throw new Error('Method not implemented.'); - } - getFontInfo(): BareFontInfo | undefined { - return BareFontInfo.createFromRawSettings({ - fontFamily: 'Monaco', - }, 1, 1, true); - } - getOutputRenderer(): OutputRenderer { - throw new Error('Method not implemented.'); - } - - changeModelDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null { - throw new Error('Method not implemented.'); - } - - deltaCellDecorations(oldDecorations: string[], newDecorations: INotebookDeltaDecoration[]): string[] { - throw new Error('Method not implemented.'); - } - - deltaCellOutputContainerClassNames(cellId: string, added: string[], removed: string[]): void { - throw new Error('Method not implemented.'); - } - - dispose() { - this._isDisposed = true; - } -} - -// export function createTestCellViewModel(instantiationService: IInstantiationService, viewType: string, notebookHandle: number, cellhandle: number, source: string[], language: string, cellKind: CellKind, outputs: IOutput[]) { -// const mockCell = new TestCell(viewType, cellhandle, source, language, cellKind, outputs); -// return createCellViewModel(instantiationService, viewType, notebookHandle, mockCell); -// } - export class NotebookEditorTestModel extends EditorModel implements INotebookEditorModel { private _dirty = false; @@ -442,7 +125,6 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi export function setupInstantiationService() { const instantiationService = new TestInstantiationService(); - instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IThemeService, new TestThemeService()); @@ -454,15 +136,10 @@ export function setupInstantiationService() { return instantiationService; } -export async function withTestNotebook(accessor: ServicesAccessor, cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => Promise | R): Promise { - - const instantiationService = accessor.get(IInstantiationService); - const undoRedoService = accessor.get(IUndoRedoService); - const textModelService = accessor.get(ITextModelService); - const bulkEditService = accessor.get(IBulkEditService); - +export async function withTestNotebook(cells: [source: string, lang: string, kind: CellKind, output?: IOutputDto[], metadata?: NotebookCellMetadata][], callback: (editor: IActiveNotebookEditor, accessor: ServicesAccessor) => Promise | R): Promise { + const instantiationService = setupInstantiationService(); const viewType = 'notebook'; - const notebook = new NotebookTextModel(viewType, URI.parse('test'), cells.map(cell => { + const notebook = instantiationService.createInstance(NotebookTextModel, viewType, URI.parse('test'), cells.map(cell => { return { source: cell[0], language: cell[1], @@ -470,13 +147,33 @@ export async function withTestNotebook(accessor: ServicesAccessor, cell outputs: cell[3] ?? [], metadata: cell[4] }; - }), notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }, undoRedoService, textModelService); + }), notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }); + const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); - const viewModel = new NotebookViewModel(viewType, model.notebook, eventDispatcher, null, instantiationService, bulkEditService, undoRedoService); - const editor = new TestNotebookEditor(viewModel); + const viewModel: NotebookViewModel = instantiationService.createInstance(NotebookViewModel, viewType, model.notebook, eventDispatcher, null); - const res = await callback(editor, viewModel, notebook); + const cellList = createNotebookCellList(instantiationService); + cellList.attachViewModel(viewModel); + const listViewInfoAccessor = new ListViewInfoAccessor(cellList); + + const notebookEditor: IActiveNotebookEditor = new class extends mock() { + onDidChangeModel: Event = new Emitter().event; + get viewModel() { return viewModel; } + hasModel(): this is IActiveNotebookEditor { + return !!this.viewModel; + } + getFocus() { return viewModel.getFocus(); } + getSelections() { return viewModel.getSelections(); } + getViewIndex(cell: ICellViewModel) { return listViewInfoAccessor.getViewIndex(cell); } + getCellRangeFromViewRange(startIndex: number, endIndex: number) { return listViewInfoAccessor.getCellRangeFromViewRange(startIndex, endIndex); } + revealCellRangeInView() { } + setHiddenAreas(_ranges: ICellRange[]): boolean { + return cellList.setHiddenAreas(_ranges, true); + } + }; + + const res = await callback(notebookEditor, instantiationService); if (res instanceof Promise) { res.finally(() => viewModel.dispose()); } else { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index bba5ee88334..a65db9f9b45 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -1010,7 +1010,7 @@ class UnsupportedSettingsRenderer extends Disposable { severity: MarkerSeverity.Hint, tags: [MarkerTag.Unnecessary], ...setting.range, - message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open local window.") + message: nls.localize('unsupportedRemoteMachineSetting', "This setting cannot be applied in this window. It will be applied when you open a local window.") }); } } diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 54129ffb298..2bd3dc22efc 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -31,6 +31,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { stripIcons } from 'vs/base/common/iconLabels'; +import { isFirefox } from 'vs/base/browser/browser'; export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { @@ -163,7 +164,7 @@ export class ShowAllCommandsAction extends Action2 { keybinding: { weight: KeybindingWeight.WorkbenchContrib, when: undefined, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P, + primary: !isFirefox ? (KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P) : undefined, secondary: [KeyCode.F1] } }); diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 34099db8509..a41b38ef4db 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -293,7 +293,7 @@ class OnAutoForwardedAction extends Disposable { } private basicMessage(tunnel: RemoteTunnel) { - return nls.localize('remote.tunnelsView.automaticForward', "Your service running on port {0} is available. ", + return nls.localize('remote.tunnelsView.automaticForward', "Your application running on port {0} is available. ", tunnel.tunnelRemotePort); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIcons.ts b/src/vs/workbench/contrib/remote/browser/remoteIcons.ts index b8a1498e2dd..99bd6a48f17 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIcons.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIcons.ts @@ -25,5 +25,6 @@ export const stopForwardIcon = registerIcon('ports-stop-forward-icon', Codicon.x export const openBrowserIcon = registerIcon('ports-open-browser-icon', Codicon.globe, nls.localize('openBrowserIcon', 'Icon for the open browser action.')); export const openPreviewIcon = registerIcon('ports-open-preview-icon', Codicon.openPreview, nls.localize('openPreviewIcon', 'Icon for the open preview action.')); export const copyAddressIcon = registerIcon('ports-copy-address-icon', Codicon.clippy, nls.localize('copyAddressIcon', 'Icon for the copy local address action.')); +export const labelPortIcon = registerIcon('ports-label-icon', Codicon.tag, nls.localize('labelPortIcon', 'Icon for the label port action.')); export const forwardedPortWithoutProcessIcon = registerIcon('ports-forwarded-without-process-icon', Codicon.circleOutline, nls.localize('forwardedPortWithoutProcessIcon', 'Icon for forwarded ports that don\'t have a running process.')); export const forwardedPortWithProcessIcon = registerIcon('ports-forwarded-with-process-icon', Codicon.circleFilled, nls.localize('forwardedPortWithProcessIcon', 'Icon for forwarded ports that do have a running process.')); diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 9cec3e1e7b3..c9c031344d3 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -33,6 +33,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private static readonly SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command private static readonly REMOTE_STATUS_LABEL_MAX_LENGTH = 40; + private static readonly CODICON_REGEXP = /\$\((.*?)\)/g; private remoteStatusEntry: IStatusbarEntryAccessor | undefined; @@ -255,10 +256,11 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr command = RemoteStatusIndicator.REMOTE_ACTIONS_COMMAND_ID; } + const ariaLabel = text.replace(RemoteStatusIndicator.CODICON_REGEXP, (_match, codiconName) => codiconName); const properties: IStatusbarEntry = { backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), - ariaLabel: name, + ariaLabel, text, showProgress, tooltip, diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 710a2ee4052..296fb1f6965 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -39,7 +39,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, publicPortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; +import { copyAddressIcon, forwardedPortWithoutProcessIcon, forwardedPortWithProcessIcon, forwardPortIcon, labelPortIcon, openBrowserIcon, openPreviewIcon, portsViewIcon, privatePortIcon, publicPortIcon, stopForwardIcon } from 'vs/workbench/contrib/remote/browser/remoteIcons'; import { IExternalUriOpenerService } from 'vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isWeb } from 'vs/base/common/platform'; @@ -696,7 +696,8 @@ export class TunnelPanel extends ViewPane { } }, getWidgetAriaLabel: () => nls.localize('tunnelView', "Tunnel View") - } + }, + openOnSingleClick: false } ) as WorkbenchTable; @@ -717,10 +718,9 @@ export class TunnelPanel extends ViewPane { rerender(); })); - // TODO@alexr00 Joao asks: why the debounce? - this._register(Event.debounce(this.table.onDidOpen, (last, event) => event, 75, true)(e => { - if (e.element && (e.element.tunnelType === TunnelType.Add)) { - this.commandService.executeCommand(ForwardPortAction.INLINE_ID); + this._register(this.table.onDidOpen(e => { + if (e.element && (e.element.tunnelType === TunnelType.Forwarded)) { + this.commandService.executeCommand(LabelTunnelAction.ID); } })); @@ -741,7 +741,9 @@ export class TunnelPanel extends ViewPane { this.table.reveal(this.table.indexOf(this.viewModel.input)); } } else { - this.table.setFocus(this.lastFocus); + if (e && (e.tunnel.tunnelType !== TunnelType.Add)) { + this.table.setFocus(this.lastFocus); + } this.focus(); } })); @@ -1329,15 +1331,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ ContextKeyExpr.or(WebContextKey.negate(), TunnelPrivacyContextKey.isEqualTo(TunnelPrivacy.Public)), ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded), TunnelTypeContextKey.isEqualTo(TunnelType.Detected))) })); -MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ - group: '0_manage', // 0_manage is used by extensions, so try not to change it - order: 0, - command: { - id: LabelTunnelAction.ID, - title: LabelTunnelAction.LABEL, - }, - when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) -})); +// The group 0_manage is used by extensions, so try not to change it MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '2_localaddress', order: 0, @@ -1402,6 +1396,15 @@ MenuRegistry.appendMenuItem(MenuId.TunnelPortInline, ({ }, when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate) })); +MenuRegistry.appendMenuItem(MenuId.TunnelPortInline, ({ + order: 1, + command: { + id: LabelTunnelAction.ID, + title: LabelTunnelAction.LABEL, + icon: labelPortIcon + }, + when: TunnelTypeContextKey.isEqualTo(TunnelType.Forwarded) +})); MenuRegistry.appendMenuItem(MenuId.TunnelPortInline, ({ order: 2, command: { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 707332a5ec5..fe5715db615 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -175,21 +175,21 @@ Registry.as(ConfigurationExtensions.Configuration) 'label': { type: 'string', description: localize('remote.portsAttributes.label', "Label that will be shown in the UI for this port."), - default: localize('remote.portsAttributes.labelDefault', "Labeled Port") + default: localize('remote.portsAttributes.labelDefault', "Application") } }, default: { - 'label': localize('remote.portsAttributes.labelDefault', "Labeled Port"), + 'label': localize('remote.portsAttributes.labelDefault', "Application"), 'onAutoForward': 'notify' } } }, - markdownDescription: localize('remote.portsAttributes', "Set properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```"), - defaultSnippets: [{ body: { '${1:3000}': { label: '${2:My Port}', onAutoForward: 'openPreview' } } }], + markdownDescription: localize('remote.portsAttributes', "Set properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```"), + defaultSnippets: [{ body: { '${1:3000}': { label: '${2:Application}', onAutoForward: 'openPreview' } } }], errorMessage: localize('remote.portsAttributes.patternError', "Must be a port number, range of port numbers, or regular expression."), additionalProperties: false }, - 'remote.portsAttributes.defaults': { + 'remote.unconfiguredPortsAttributes': { type: 'object', properties: { 'onAutoForward': { @@ -213,13 +213,11 @@ Registry.as(ConfigurationExtensions.Configuration) 'label': { type: 'string', description: localize('remote.portsAttributes.label', "Label that will be shown in the UI for this port."), - default: localize('remote.portsAttributes.labelDefault', "Labeled Port") + default: localize('remote.portsAttributes.labelDefault', "Application") } }, - default: { - 'onAutoForward': 'notify' - }, - markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"notify\"\n}\n```"), + defaultSnippets: [{ body: { onAutoForward: 'ignore' } }], + markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"), additionalProperties: false } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 24a77eaec85..9061009b2ed 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -313,6 +313,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes = this._register(this.instantiationService.createInstance(IncludePatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: nls.localize('label.includes', 'Search Include Patterns'), + placeholder: nls.localize('placeholder.includes', "(e.g. *.ts, src/**/include)"), history: patternIncludesHistory, })); @@ -330,6 +331,7 @@ export class SearchView extends ViewPane { dom.append(excludesList, $('h4', undefined, excludesTitle)); this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { ariaLabel: nls.localize('label.excludes', 'Search Exclude Patterns'), + placeholder: nls.localize('placeholder.excludes', "(e.g. *.ts, src/**/exclude)"), history: patternExclusionsHistory, })); diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 5e6c6f746fb..f1abc80d5d3 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -44,7 +44,7 @@ export abstract class AbstractProblemCollector implements IDisposable { private buffer: string[]; private bufferLength: number; private openModels: IStringDictionary; - private readonly modelListeners = new DisposableStore(); + protected readonly modelListeners = new DisposableStore(); private tail: Promise | undefined; // [owner] -> ApplyToKind @@ -58,7 +58,7 @@ export abstract class AbstractProblemCollector implements IDisposable { protected _onDidStateChange: Emitter; - constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, private modelService: IModelService, fileService?: IFileService) { + constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, protected modelService: IModelService, fileService?: IFileService) { this.matchers = Object.create(null); this.bufferLength = 1; problemMatchers.map(elem => createLineMatcher(elem, fileService)).forEach((matcher) => { @@ -406,6 +406,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private currentOwner: string | undefined; private currentResource: string | undefined; + private lines: string[] = []; + constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, fileService?: IFileService) { super(problemMatchers, markerService, modelService, fileService); this.problemMatchers = problemMatchers; @@ -423,6 +425,27 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement }); } }); + + this.modelListeners.add(this.modelService.onModelRemoved(modelEvent => { + let markerChanged: IDisposable | undefined = + Event.debounce(this.markerService.onMarkerChanged, (last: readonly URI[] | undefined, e: readonly URI[]) => { + return (last ?? []).concat(e); + }, 500)(async (markerEvent) => { + markerChanged?.dispose(); + markerChanged = undefined; + if (!markerEvent.includes(modelEvent.uri)) { + return; + } + const oldLines = Array.from(this.lines); + for (const line of oldLines) { + await this.processLineInternal(line); + } + }); + setTimeout(async () => { + markerChanged?.dispose(); + markerChanged = undefined; + }, 600); + })); } public aboutToStart(): void { @@ -439,6 +462,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement if (await this.tryBegin(line) || this.tryFinish(line)) { return; } + this.lines.push(line); let markerMatch = this.tryFindMarker(line); if (!markerMatch) { return; @@ -472,6 +496,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement } this._activeBackgroundMatchers.add(background.key); result = true; + this.lines = []; + this.lines.push(line); this._onDidStateChange.fire(ProblemCollectorEvent.create(ProblemCollectorEventKind.BackgroundProcessingBegins)); this.cleanMarkerCaches(); this.resetCurrentResource(); @@ -498,6 +524,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement this.resetCurrentResource(); this._onDidStateChange.fire(ProblemCollectorEvent.create(ProblemCollectorEventKind.BackgroundProcessingEnds)); result = true; + this.lines.push(line); let owner = background.matcher.owner; this.cleanMarkers(owner); this.cleanMarkerCaches(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 242a5289199..669bcec8f79 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IOffProcessTerminalService, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensions, ITerminalLaunchError, ITerminalTabLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal'; -import { IAvailableShellsRequest, ICommandTracker, IDefaultShellAndArgsRequest, INavigationMode, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, LinuxDistro, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IAvailableProfilesRequest, ICommandTracker, IDefaultShellAndArgsRequest, INavigationMode, IRemoteTerminalAttachTarget, ITerminalProfile, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, LinuxDistro, TitleEventSource } 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'; @@ -96,9 +96,10 @@ export interface ITerminalService { onInstancesChanged: Event; onInstanceTitleChanged: Event; onActiveInstanceChanged: Event; - onRequestAvailableShells: Event; + onRequestAvailableProfiles: Event; onDidRegisterProcessSupport: Event; onDidChangeConnectionState: Event; + onProfilesConfigChanged: Event; /** * Creates a terminal. @@ -154,7 +155,12 @@ export interface ITerminalService { */ registerLinkProvider(linkProvider: ITerminalExternalLinkProvider): IDisposable; - selectDefaultShell(): Promise; + selectDefaultProfile(): Promise; + + /** + * Gets the detected terminal profiles for the platform + */ + getAvailableProfiles(): ITerminalProfile[]; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; manageWorkspaceShellPermissions(): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 8a1b69ac752..784a74d1990 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -21,7 +21,7 @@ import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/com import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ILocalTerminalService } from 'vs/platform/terminal/common/terminal'; @@ -30,7 +30,7 @@ import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/w import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions'; import { Direction, IRemoteTerminalService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; -import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TERMINAL_COMMAND_ID, TERMINAL_VIEW_ID, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_ALT_BUFFER_ACTIVE, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_ACTION_CATEGORY, TERMINAL_COMMAND_ID, TERMINAL_VIEW_ID, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -38,7 +38,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export const switchTerminalActionViewItemSeparator = '─────────'; -export const selectDefaultShellTitle = localize('workbench.action.terminal.selectDefaultShell', "Select Default Shell"); +export const selectDefaultProfileTitle = localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"); export const configureTerminalSettingsTitle = localize('workbench.action.terminal.openSettings', "Configure Terminal Settings"); const enum ContextMenuGroup { @@ -1273,7 +1273,7 @@ export function registerTerminalActions() { title: { value: localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"), original: 'Kill the Active Terminal Instance' }, f1: true, category, - precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, + precondition: ContextKeyExpr.or(KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN), icon: Codicon.trash, menu: { id: MenuId.ViewTitle, @@ -1332,15 +1332,15 @@ export function registerTerminalActions() { registerAction2(class extends Action2 { constructor() { super({ - id: TERMINAL_COMMAND_ID.SELECT_DEFAULT_SHELL, - title: { value: localize('workbench.action.terminal.selectDefaultShell', "Select Default Shell"), original: 'Select Default Shell' }, + id: TERMINAL_COMMAND_ID.SELECT_DEFAULT_PROFILE, + title: { value: localize('workbench.action.terminal.selectDefaultProfile', "Select Default Profile"), original: 'Select Default Profile' }, f1: true, category, precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED }); } async run(accessor: ServicesAccessor) { - await accessor.get(ITerminalService).selectDefaultShell(); + await accessor.get(ITerminalService).selectDefaultProfile(); } }); registerAction2(class extends Action2 { @@ -1438,6 +1438,7 @@ export function registerTerminalActions() { async run(accessor: ServicesAccessor, item?: string) { const terminalService = accessor.get(ITerminalService); const terminalContributionService = accessor.get(ITerminalContributionService); + const notificationService = accessor.get(INotificationService); const commandService = accessor.get(ICommandService); if (!item || !item.split) { return Promise.resolve(null); @@ -1446,9 +1447,9 @@ export function registerTerminalActions() { terminalService.refreshActiveTab(); return Promise.resolve(null); } - if (item === selectDefaultShellTitle) { + if (item === selectDefaultProfileTitle) { terminalService.refreshActiveTab(); - return terminalService.selectDefaultShell(); + return terminalService.selectDefaultProfile(); } if (item === configureTerminalSettingsTitle) { await commandService.executeCommand(TERMINAL_COMMAND_ID.CONFIGURE_TERMINAL_SETTINGS); @@ -1459,13 +1460,43 @@ export function registerTerminalActions() { terminalService.setActiveTabByIndex(Number(indexMatches[1]) - 1); return terminalService.showPanel(true); } + const detectedProfiles = await terminalService.getAvailableProfiles(); - const customType = terminalContributionService.terminalTypes.find(t => t.title === item); - if (customType) { - return commandService.executeCommand(customType.command); + // Remove 'New ' from the selected item to get the profile name + const profileSelection = item.substring(4); + + if (detectedProfiles) { + let launchConfig = detectedProfiles?.find((profile: { profileName: string; }) => profile.profileName === profileSelection); + if (launchConfig && !launchConfig.isWorkspaceProfile) { + const instance = terminalService.createTerminal({ executable: launchConfig.path, name: launchConfig.profileName, args: launchConfig.args }); + terminalService.setActiveInstance(instance); + } else if (launchConfig && launchConfig.isWorkspaceProfile) { + notificationService.prompt(Severity.Info, `Do you allow this workspace to modify your terminal profile? ${item}`, + [{ + label: 'Allow', + run: () => { + const instance = terminalService.createTerminal({ executable: launchConfig?.path, name: launchConfig?.profileName, args: launchConfig?.args }); + terminalService.setActiveInstance(instance); + } + }, + { + label: 'Disallow', + run: () => { + const activeInstance = terminalService.getActiveInstance(); + if (activeInstance) { + terminalService.setActiveInstance(activeInstance); + } + } + }] + ); + } + } else { + const customType = terminalContributionService.terminalTypes.find(t => t.title === item); + if (customType) { + return commandService.executeCommand(customType.command); + } + console.warn(`Unmatched terminal item: "${item}"`); } - - console.warn(`Unmatched terminal item: "${item}"`); return Promise.resolve(); } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 8ae511a0893..f7d1510209d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -220,16 +220,18 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { const shellConfigValue = this._configurationService.inspect(`terminal.integrated.shell.${platformKey}`); const shellArgsConfigValue = this._configurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); const envConfigValue = this._configurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`); + const profiles = this._configurationService.inspect<{ [key: string]: string }>(`terminal.integrated.profiles.${platformKey}`); // Check if workspace setting exists and whether it's allowed let isWorkspaceShellAllowed: boolean | undefined = false; - if (shellConfigValue.workspaceValue !== undefined || shellArgsConfigValue.workspaceValue !== undefined || envConfigValue.workspaceValue !== undefined) { + if (shellConfigValue.workspaceValue !== undefined || shellArgsConfigValue.workspaceValue !== undefined || envConfigValue.workspaceValue !== undefined || profiles.workspaceValue !== undefined) { isWorkspaceShellAllowed = this.isWorkspaceShellAllowed(undefined); } // Always allow [] args as it would lead to an odd error message and should not be dangerous if (shellConfigValue.workspaceValue === undefined && envConfigValue.workspaceValue === undefined && - shellArgsConfigValue.workspaceValue && shellArgsConfigValue.workspaceValue.length === 0) { + shellArgsConfigValue.workspaceValue && shellArgsConfigValue.workspaceValue.length === 0 + && profiles.workspaceValue === undefined) { isWorkspaceShellAllowed = true; } @@ -248,6 +250,10 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { if (envConfigValue.workspaceValue) { envString = `env: {${Object.keys(envConfigValue.workspaceValue).map(k => `${k}:${envConfigValue.workspaceValue![k]}`).join(', ')}}`; } + let profilesString: string | undefined; + if (profiles.workspaceValue) { + profilesString = `profiles: {${Object.keys(profiles.workspaceValue).map(k => `${k}:${profiles.workspaceValue![k]}`).join(', ')}}`; + } // Should not be localized as it's json-like syntax referencing settings keys const workspaceConfigStrings: string[] = []; if (shellString) { @@ -259,6 +265,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { if (envString) { workspaceConfigStrings.push(envString); } + if (profilesString) { + workspaceConfigStrings.push(profilesString); + } const workspaceConfigString = workspaceConfigStrings.join(', '); this._notificationService.prompt(Severity.Info, nls.localize('terminal.integrated.allowWorkspaceShell', "Do you allow this workspace to modify your terminal shell? {0}", workspaceConfigString), [{ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ec9401662fa..c1bf4d5dcd8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -48,6 +48,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType } from 'vs/platform/terminal/common/terminal'; +import { IProductService } from 'vs/platform/product/common/productService'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -202,7 +203,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @ILogService private readonly _logService: ILogService, @IStorageService private readonly _storageService: IStorageService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + @IProductService private readonly _productService: IProductService ) { super(); @@ -576,7 +578,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { !TERMINAL_CREATION_COMMANDS.includes(resolveResult.commandId)) { this._notificationService.prompt( Severity.Info, - nls.localize('configure terminal settings', "Some keybindings are dispatched to the workbench by default."), + nls.localize('keybindingHandling', "Some keybindings don't go to the terminal by default and are handled by {0} instead.", this._productService.nameLong), [ { label: nls.localize('configureTerminalSettings', "Configure Terminal Settings"), @@ -1127,16 +1129,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - @debounce(500) public reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): void { // Unsubscribe any key listener we may have. this._pressAnyKeyToCloseListener?.dispose(); this._pressAnyKeyToCloseListener = undefined; if (this._xterm) { - if (reset) { - this._xterm.reset(); - } else { + if (!reset) { // Ensure new processes' output starts at start of new line this._xterm.write('\n\x1b[G'); } @@ -1168,11 +1167,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Set the new shell launch config this._shellLaunchConfig = shell; // Must be done before calling _createProcess() - this._processManager.relaunch(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()); + this._processManager.relaunch(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized(), reset); - this._xtermTypeAhead?.reset(this._processManager); + this._xtermTypeAhead?.reset(); } + @debounce(1000) public relaunch(): void { this.reuseTerminal(this._shellLaunchConfig, true); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 6921fc2911c..4a92c9d82ae 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -28,6 +28,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { URI } from 'vs/base/common/uri'; import { IEnvironmentVariableInfo, IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IProcessDataEvent, IShellLaunchConfig, ITerminalChildProcess, ITerminalDimensionsOverride, ITerminalEnvironment, ITerminalLaunchError, FlowControlConstants, TerminalShellType, ILocalTerminalService, IOffProcessTerminalService } from 'vs/platform/terminal/common/terminal'; +import { TerminalRecorder } from 'vs/platform/terminal/common/terminalRecorder'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -72,6 +73,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce private _hasWrittenData: boolean = false; private _ptyResponsiveListener: IDisposable | undefined; private _ptyListenersAttached: boolean = false; + private _dataFilter: SeamlessRelaunchDataFilter; private readonly _onPtyDisconnect = this._register(new Emitter()); public get onPtyDisconnect(): Event { return this._onPtyDisconnect.event; } @@ -124,14 +126,19 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce super(); this._localTerminalService = localTerminalService; - this.ptyProcessReady = new Promise(c => { - this.onProcessReady(() => { - this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); - c(undefined); - }); - }); + this.ptyProcessReady = this._createPtyProcessReadyPromise(); this.getLatency(); this._ackDataBufferer = new AckDataBufferer(e => this._process?.acknowledgeDataEvent(e)); + this._dataFilter = this._instantiationService.createInstance(SeamlessRelaunchDataFilter); + this._dataFilter.onProcessData(ev => { + const data = (typeof ev === 'string' ? ev : ev.data); + const sync = (typeof ev === 'string' ? false : ev.sync); + const beforeProcessDataEvent: IBeforeProcessDataEvent = { data }; + this._onBeforeProcessData.fire(beforeProcessDataEvent); + if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) { + this._onProcessData.fire({ data: beforeProcessDataEvent.data, sync }); + } + }); } public dispose(immediate: boolean = false): void { @@ -147,6 +154,16 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce super.dispose(); } + private _createPtyProcessReadyPromise(): Promise { + return new Promise(c => { + const listener = this.onProcessReady(() => { + this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); + listener.dispose(); + c(undefined); + }); + }); + } + public detachFromProcess(): void { if (this._process?.detach) { this._process.detach(); @@ -157,7 +174,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, - isScreenReaderModeEnabled: boolean + isScreenReaderModeEnabled: boolean, + reset: boolean = true ): Promise { if (shellLaunchConfig.isExtensionCustomPtyTerminal) { this._processType = ProcessType.ExtensionTerminal; @@ -238,15 +256,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this.processState = ProcessState.LAUNCHING; - this._process.onProcessData(ev => { - const data = (typeof ev === 'string' ? ev : ev.data); - const sync = (typeof ev === 'string' ? false : ev.sync); - const beforeProcessDataEvent: IBeforeProcessDataEvent = { data }; - this._onBeforeProcessData.fire(beforeProcessDataEvent); - if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) { - this._onProcessData.fire({ data: beforeProcessDataEvent.data, sync }); - } - }); + this._dataFilter.newProcess(this._process, reset); this._process.onProcessReady((e: { pid: number, cwd: string }) => { this.shellProcessId = e.pid; @@ -285,14 +295,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce return undefined; } - public async relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise { - this.ptyProcessReady = new Promise(c => { - this.onProcessReady(() => { - this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); - c(undefined); - }); - }); - return this.createProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled); + public async relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean, reset: boolean): Promise { + this.ptyProcessReady = this._createPtyProcessReadyPromise(); + this._logService.trace(`Relaunching terminal instance ${this._instanceId}`); + return this.createProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled, reset); } // Fetch any extension environment additions and apply them @@ -431,6 +437,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce public async write(data: string): Promise { await this.ptyProcessReady; + this._dataFilter.triggerSwap(); this._hasWrittenData = true; if (this.shellProcessId || this._processType === ProcessType.ExtensionTerminal) { if (this._process) { @@ -516,3 +523,151 @@ class AckDataBufferer { } } } + +const enum SeamlessRelaunchConstants { + /** + * How long to record data events for new terminals. + */ + RecordTerminalDuration = 10000, + /** + * The maximum duration after a relaunch occurs to trigger a swap. + */ + SwapWaitMaximumDuration = 3000 +} + +/** + * Filters data events from the process and supports seamlessly restarting swapping out the process + * with another, delaying the swap in output in order to minimize flickering/clearing of the + * terminal. + */ +class SeamlessRelaunchDataFilter extends Disposable { + private _firstRecorder?: TerminalRecorder; + private _secondRecorder?: TerminalRecorder; + private _firstDisposable?: IDisposable; + private _secondDisposable?: IDisposable; + private _dataListener?: IDisposable; + private _activeProcess?: ITerminalChildProcess; + + private _recordingTimeout?: number; + private _swapTimeout?: number; + + private readonly _onProcessData = this._register(new Emitter()); + public get onProcessData(): Event { return this._onProcessData.event; } + + constructor( + @ILogService private readonly _logService: ILogService + ) { + super(); + } + + newProcess(process: ITerminalChildProcess, reset: boolean) { + // Stop listening to the old process + this._dataListener?.dispose(); + this._activeProcess = process; + + // If the process is new, relaunch has timed out or the terminal should not reset, start + // listening and firing data events immediately + if (!this._firstRecorder || !reset) { + this._firstDisposable?.dispose(); + [this._firstRecorder, this._firstDisposable] = this._createRecorder(process); + this._dataListener = process.onProcessData(e => this._onProcessData.fire(e)); + if (this._recordingTimeout) { + window.clearTimeout(this._recordingTimeout); + } + this._recordingTimeout = window.setTimeout(() => this._stopRecording(), SeamlessRelaunchConstants.RecordTerminalDuration); + return; + } + + // Trigger a swap if there was a recent relaunch + if (this._secondRecorder) { + this.triggerSwap(); + } + + this._swapTimeout = window.setTimeout(() => this.triggerSwap(), SeamlessRelaunchConstants.SwapWaitMaximumDuration); + + // Pause all outgoing data events + this._dataListener?.dispose(); + + this._firstDisposable?.dispose(); + const recorder = this._createRecorder(process); + [this._secondRecorder, this._secondDisposable] = recorder; + } + + /** + * Trigger the swap of the processes if needed (eg. timeout, input) + */ + triggerSwap() { + // Clear the swap timeout if it exists + if (this._swapTimeout) { + window.clearTimeout(this._swapTimeout); + this._swapTimeout = undefined; + } + + // Do nothing if there's nothing being recorder + if (!this._firstRecorder) { + return; + } + + // Clear the first recorder if no second process was attached before the swap trigger + if (!this._secondRecorder) { + this._firstRecorder = undefined; + this._firstDisposable?.dispose(); + return; + } + + // Generate data for each recorder + const firstData = this._getDataFromRecorder(this._firstRecorder); + const secondData = this._getDataFromRecorder(this._secondRecorder); + + // Re-write the terminal if the data differs + if (firstData === secondData) { + this._logService.trace(`Seamless terminal relaunch - identical content`); + } else { + this._logService.trace(`Seamless terminal relaunch - resetting content`); + // Fire full reset (RIS) followed by the new data so the update happens in the same frame + this._onProcessData.fire({ data: `\x1bc${secondData}`, sync: false }); + } + + // Set up the new data listener + this._dataListener?.dispose(); + this._dataListener = this._activeProcess!.onProcessData(e => this._onProcessData.fire(e)); + + // Replace first recorder with second + this._firstRecorder = this._secondRecorder; + this._firstDisposable?.dispose(); + this._firstDisposable = this._secondDisposable; + if (this._recordingTimeout) { + window.clearTimeout(this._recordingTimeout); + } + this._recordingTimeout = window.setTimeout(() => this._stopRecording(), SeamlessRelaunchConstants.RecordTerminalDuration); + } + + private _stopRecording() { + // Continue recording if a swap is coming + if (this._swapTimeout) { + return; + } + + // Clear the timeout + if (this._recordingTimeout) { + window.clearTimeout(this._recordingTimeout); + this._recordingTimeout = undefined; + } + + // Stop recording + this._firstRecorder = undefined; + this._firstDisposable?.dispose(); + this._secondRecorder = undefined; + this._secondDisposable?.dispose(); + } + + private _createRecorder(process: ITerminalChildProcess): [TerminalRecorder, IDisposable] { + const recorder = new TerminalRecorder(0, 0); + const disposable = process.onProcessData(e => recorder.recordData(typeof e === 'string' ? e : e.data)); + return [recorder, disposable]; + } + + private _getDataFromRecorder(recorder: TerminalRecorder): string { + return recorder.generateReplayEvent().events.filter(e => !!e.data).map(e => e.data).join(''); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 09cd2b8843b..4060fc3b10d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { timeout } from 'vs/base/common/async'; -import { debounce } from 'vs/base/common/decorators'; +import { debounce, throttle } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; @@ -24,9 +24,10 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term 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, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, 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 { IAvailableProfilesRequest, IRemoteTerminalAttachTarget, ITerminalProfile, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, 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, ITerminalProfileObject, ITerminalExecutable, ITerminalProfileSource } 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'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService, ShutdownReason, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -68,6 +69,8 @@ export class TerminalService implements ITerminalService { private _localTerminalsInitPromise: Promise | undefined; private _connectionState: TerminalConnectionState; + private _availableProfiles: ITerminalProfile[] | undefined; + public get configHelper(): ITerminalConfigHelper { return this._configHelper; } private readonly _onActiveTabChanged = new Emitter(); @@ -94,14 +97,15 @@ export class TerminalService implements ITerminalService { public get onActiveInstanceChanged(): Event { return this._onActiveInstanceChanged.event; } private readonly _onTabDisposed = new Emitter(); public get onTabDisposed(): Event { return this._onTabDisposed.event; } - private readonly _onRequestAvailableShells = new Emitter(); - public get onRequestAvailableShells(): Event { return this._onRequestAvailableShells.event; } + private readonly _onRequestAvailableProfiles = new Emitter(); + public get onRequestAvailableProfiles(): Event { return this._onRequestAvailableProfiles.event; } private readonly _onDidRegisterProcessSupport = new Emitter(); public get onDidRegisterProcessSupport(): Event { return this._onDidRegisterProcessSupport.event; } private readonly _onDidChangeConnectionState = new Emitter(); public get onDidChangeConnectionState(): Event { return this._onDidChangeConnectionState.event; } public get connectionState(): TerminalConnectionState { return this._connectionState; } - + private readonly _onProfilesConfigChanged = new Emitter(); + public get onProfilesConfigChanged(): Event { return this._onProfilesConfigChanged.event; } private readonly _localTerminalService?: ILocalTerminalService; constructor( @@ -118,6 +122,7 @@ export class TerminalService implements ITerminalService { @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IExtensionService private readonly _extensionService: IExtensionService, @optional(ILocalTerminalService) localTerminalService: ILocalTerminalService ) { this._localTerminalService = localTerminalService; @@ -142,16 +147,34 @@ export class TerminalService implements ITerminalService { this._processSupportContextKey = KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED.bindTo(this._contextKeyService); this._processSupportContextKey.set(!isWeb || this._remoteAgentService.getConnection() !== null); + this._configurationService.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('terminal.integrated.profiles.windows') || + e.affectsConfiguration('terminal.integrated.profiles.osx') || + e.affectsConfiguration('terminal.integrated.profiles.linux') || + e.affectsConfiguration('terminal.integrated.detectWslProfiles')) { + this._onProfilesConfigChanged.fire(); + } + }); + const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions; + // Connect to the extension host if it's there, set the connection state to connected when + // it's done. This should happen even when there is no extension host. this._connectionState = TerminalConnectionState.Connecting; + let initPromise: Promise; if (!!this._environmentService.remoteAuthority && enableTerminalReconnection) { - this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals(); + initPromise = this._remoteTerminalsInitPromise = this._reconnectToRemoteTerminals(); } else if (enableTerminalReconnection) { - this._localTerminalsInitPromise = this._reconnectToLocalTerminals(); + initPromise = this._localTerminalsInitPromise = this._reconnectToLocalTerminals(); } else { - this._connectionState = TerminalConnectionState.Connected; + initPromise = Promise.resolve(); } + initPromise.then(() => this._setConnected()); + } + + private _setConnected() { + this._connectionState = TerminalConnectionState.Connected; + this._onDidChangeConnectionState.fire(); } private async _reconnectToRemoteTerminals(): Promise { @@ -167,11 +190,9 @@ export class TerminalService implements ITerminalService { count: reconnectCounter }; this._telemetryService.publicLog('terminalReconnection', data); - this._connectionState = TerminalConnectionState.Connected; // now that terminals have been restored, // attach listeners to update remote when terminals are changed this.attachProcessLayoutListeners(true); - this._onDidChangeConnectionState.fire(); } private async _reconnectToLocalTerminals(): Promise { @@ -186,8 +207,6 @@ export class TerminalService implements ITerminalService { // now that terminals have been restored, // attach listeners to update local state when terminals are changed this.attachProcessLayoutListeners(false); - this._connectionState = TerminalConnectionState.Connected; - this._onDidChangeConnectionState.fire(); } private _recreateTerminalTabs(layoutInfo?: ITerminalsLayoutInfo): number { @@ -271,6 +290,77 @@ export class TerminalService implements ITerminalService { this._extHostsReady[remoteAuthority]!.resolve(); } + public getAvailableProfiles(): ITerminalProfile[] { + this._updateAvailableProfiles(); + return this._availableProfiles?.map(s => ({ profileName: s.profileName, path: s.path, args: s.args, isWorkspaceProfile: this._getWorkspaceProfilePermissions(s) } as ITerminalProfile)) || []; + } + + private _getWorkspaceProfilePermissions(profile: ITerminalProfile): boolean { + if (isWindows) { + const profiles = this._configurationService.inspect<{ [key: string]: ITerminalProfileObject }>(`terminal.integrated.profiles.windows`); + if (profiles && profiles.workspaceValue && profiles.defaultValue) { + const workspaceProfile = Object.entries(profiles.workspaceValue).find(p => p[0] === profile.profileName); + const defaultProfile = Object.entries(profiles.defaultValue).find(p => p[0] === profile.profileName); + if (workspaceProfile && defaultProfile && workspaceProfile[0] === defaultProfile[0]) { + let result = !this._terminalProfileObjectEqual(workspaceProfile[1], defaultProfile[1]); + return result; + } else if (!workspaceProfile && !defaultProfile) { + // user profile + return false; + } else { + // this key is missing from either default or the workspace config + return true; + } + } + } + return false; + } + + private _terminalProfileObjectEqual(one?: ITerminalProfileObject, two?: ITerminalProfileObject): boolean { + if (one === null && two === null) { + return true; + } else if ((one as ITerminalExecutable).path && (two as ITerminalExecutable).path) { + const oneExec = (one as ITerminalExecutable); + const twoExec = (two as ITerminalExecutable); + return ((Array.isArray(oneExec.path) && Array.isArray(twoExec.path) && oneExec.path.length === twoExec.path.length && oneExec.path.every((p, index) => p === twoExec.path[index])) || + (oneExec.path === twoExec.path) + ) && ((Array.isArray(oneExec.args) && Array.isArray(twoExec.args) && oneExec.args?.every((a, index) => a === twoExec.args?.[index])) || + (oneExec.args === twoExec.args) + ); + } else if ((one as ITerminalProfileSource).source && (two as ITerminalProfileSource).source) { + const oneSource = (one as ITerminalProfileSource); + const twoSource = (two as ITerminalProfileSource); + return oneSource.source === twoSource.source; + } + return false; + } + + // avoid checking this very often, every ten seconds shoulds suffice + @throttle(10000) + private async _updateAvailableProfiles(): Promise { + const result = await this._detectProfiles(true); + if (result !== this._availableProfiles) { + this._availableProfiles = result; + this._onProfilesConfigChanged.fire(); + } + } + + private async _detectProfiles(quickLaunchOnly: boolean): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); + // Wait for the remoteAuthority to be ready (and listening for events) before firing + // the event to spawn the ext host process + const conn = this._remoteAgentService.getConnection(); + const remoteAuthority = conn ? conn.remoteAuthority : 'null'; + await this._whenExtHostReady(remoteAuthority); + return new Promise(r => this._onRequestAvailableProfiles.fire({ callback: r, quickLaunchOnly: quickLaunchOnly })); + } + + private async _whenExtHostReady(remoteAuthority: string): Promise { + this._createExtHostReadyEntry(remoteAuthority); + this._updateAvailableProfiles(); + return this._extHostsReady[remoteAuthority]!.promise; + } + private _createExtHostReadyEntry(remoteAuthority: string): void { if (this._extHostsReady[remoteAuthority]) { return; @@ -563,7 +653,7 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onLinksReady(this._onInstanceLinksReady.fire, this._onInstanceLinksReady)); instance.addDisposable(instance.onDimensionsChanged(() => { this._onInstanceDimensionsChanged.fire(instance); - if (this.configHelper.config.enablePersistentSessions) { + if (this.configHelper.config.enablePersistentSessions && this.isProcessSupportRegistered) { !!this._environmentService.remoteAuthority ? this._updateRemoteState() : this._updateLocalState(); } })); @@ -732,19 +822,19 @@ export class TerminalService implements ITerminalService { }); } - public async selectDefaultShell(): Promise { - const shells = await this._detectShells(); + public async selectDefaultProfile(): Promise { + const profiles = await this._detectProfiles(false); const options: IPickOptions = { placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") }; - const quickPickItems = shells.map((s): IQuickPickItem => { - return { label: s.label, description: s.path }; + const quickPickItems = profiles.map((p): IQuickPickItem => { + return { label: p.profileName, description: p.path }; }); const value = await this._quickInputService.pick(quickPickItems, options); if (!value) { return undefined; } - const shell = value.description; + const profile = value.description; const env = await this._remoteAgentService.getEnvironment(); let platformKey: string; if (env) { @@ -752,11 +842,7 @@ export class TerminalService implements ITerminalService { } else { platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux'); } - await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER); - } - - private _detectShells(): Promise { - return new Promise(r => this._onRequestAvailableShells.fire({ callback: r })); + await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, profile, ConfigurationTarget.USER); } public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { @@ -773,7 +859,7 @@ export class TerminalService implements ITerminalService { } public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { - if (!this.isProcessSupportRegistered) { + if (!shell.isExtensionCustomPtyTerminal && !this.isProcessSupportRegistered) { throw new Error('Could not create terminal when process support is not registered'); } if (shell.hideFromUser) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts index 835915af1b9..e59ee62c274 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts @@ -1330,10 +1330,8 @@ export class TypeAheadAddon extends Disposable implements ITerminalAddon { })); } - public reset(processManager: ITerminalProcessManager) { + public reset() { this.lastRow = undefined; - this.processManager = processManager; - this._register(this.processManager.onBeforeProcessData(e => this.onBeforeProcessData(e))); } private deferClearingPredictions() { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index dddc971477b..b752ef83738 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -13,7 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService, IColorTheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; -import { configureTerminalSettingsTitle, selectDefaultShellTitle, switchTerminalActionViewItemSeparator } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { configureTerminalSettingsTitle, selectDefaultProfileTitle, switchTerminalActionViewItemSeparator } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { URI } from 'vs/base/common/uri'; import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -34,7 +34,6 @@ import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/comm import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; 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'; @@ -50,6 +49,7 @@ export class TerminalViewPane extends ViewPane { private _findWidget: TerminalFindWidget | undefined; private _terminalsInitialized = false; private _bodyDimensions: { width: number, height: number } = { width: 0, height: 0 }; + private _isWelcomeShowing: boolean = false; constructor( options: IViewPaneOptions, @@ -76,13 +76,21 @@ export class TerminalViewPane extends ViewPane { } this._onDidChangeViewWelcomeState.fire(); }); + this._terminalService.onInstanceCreated(() => { + if (!this._isWelcomeShowing) { + return; + } + this._isWelcomeShowing = true; + this._onDidChangeViewWelcomeState.fire(); + if (this._terminalContainer) { + this._terminalContainer.style.display = 'block'; + this.layoutBody(this._terminalContainer.offsetHeight, this._terminalContainer.offsetWidth); + } + }); } protected renderBody(container: HTMLElement): void { super.renderBody(container); - if (this.shouldShowWelcome()) { - return; - } this._parentDomElement = container; this._parentDomElement.classList.add('integrated-terminal'); @@ -90,6 +98,7 @@ export class TerminalViewPane extends ViewPane { this._terminalContainer = document.createElement('div'); this._terminalContainer.classList.add('terminal-outer-container'); + this._terminalContainer.style.display = this.shouldShowWelcome() ? 'none' : 'block'; this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState()); this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS)); @@ -150,9 +159,6 @@ export class TerminalViewPane extends ViewPane { protected layoutBody(height: number, width: number): void { super.layoutBody(height, width); - if (this.shouldShowWelcome()) { - return; - } this._bodyDimensions.width = width; this._bodyDimensions.height = height; @@ -339,7 +345,8 @@ export class TerminalViewPane extends ViewPane { } shouldShowWelcome(): boolean { - return !this._terminalService.isProcessSupportRegistered; + this._isWelcomeShowing = !this._terminalService.isProcessSupportRegistered && this._terminalService.terminalInstances.length === 0; + return this._isWelcomeShowing; } } @@ -358,22 +365,21 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = class SwitchTerminalActionViewItem extends SelectActionViewItem { - private _lastOptions: ISelectOptionItem[] = []; - private _lastActiveTab: number = 0; constructor( action: IAction, @ITerminalService private readonly _terminalService: ITerminalService, @IThemeService private readonly _themeService: IThemeService, @ITerminalContributionService private readonly _contributions: ITerminalContributionService, - @IContextViewService contextViewService: IContextViewService, + @IContextViewService contextViewService: IContextViewService ) { super(null, action, getTerminalSelectOpenItems(_terminalService, _contributions), _terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.'), optionsAsChildren: true }); - - this._register(_terminalService.onInstancesChanged(this._updateItems, this)); - this._register(_terminalService.onActiveTabChanged(this._updateItems, this)); - this._register(_terminalService.onInstanceTitleChanged(this._updateItems, this)); - this._register(_terminalService.onTabDisposed(this._updateItems, this)); - this._register(_terminalService.onDidChangeConnectionState(this._updateItems, this)); + this._register(_terminalService.onInstancesChanged(() => this._updateItems(), this)); + this._register(_terminalService.onActiveTabChanged(() => this._updateItems(), this)); + this._register(_terminalService.onInstanceTitleChanged(() => this._updateItems(), this)); + this._register(_terminalService.onTabDisposed(() => this._updateItems(), this)); + this._register(_terminalService.onDidChangeConnectionState(() => this._updateItems(), this)); + this._register(_terminalService.onProfilesConfigChanged(() => this._updateItems(), this)); + this._register(_terminalService.onRequestAvailableProfiles(() => this._updateItems(), this)); this._register(attachSelectBoxStyler(this.selectBox, this._themeService)); } @@ -387,18 +393,12 @@ class SwitchTerminalActionViewItem extends SelectActionViewItem { private _updateItems(): void { const options = getTerminalSelectOpenItems(this._terminalService, this._contributions); - // only update options if they've changed - if (!equals(Object.values(options), Object.values(this._lastOptions)) || this._lastActiveTab !== this._terminalService.activeTabIndex) { - this.setOptions(options, this._terminalService.activeTabIndex); - this._lastOptions = options; - this._lastActiveTab = this._terminalService.activeTabIndex; - } + this.setOptions(options, this._terminalService.activeTabIndex); } } function getTerminalSelectOpenItems(terminalService: ITerminalService, contributions: ITerminalContributionService): ISelectOptionItem[] { let items: ISelectOptionItem[]; - if (terminalService.connectionState === TerminalConnectionState.Connected) { items = terminalService.getTabLabels().map(label => { return { text: label }; @@ -409,11 +409,18 @@ function getTerminalSelectOpenItems(terminalService: ITerminalService, contribut items.push({ text: switchTerminalActionViewItemSeparator, isDisabled: true }); + items.push(...getProfileSelectOptionItems(terminalService)); + for (const contributed of contributions.terminalTypes) { items.push({ text: contributed.title }); } - - items.push({ text: selectDefaultShellTitle }); + items.push({ text: switchTerminalActionViewItemSeparator, isDisabled: true }); + items.push({ text: selectDefaultProfileTitle }); items.push({ text: configureTerminalSettingsTitle }); return items; } + +function getProfileSelectOptionItems(terminalService: ITerminalService): ISelectOptionItem[] { + const detectedProfiles = terminalService.getAvailableProfiles(); + return detectedProfiles?.map((shell: { profileName: string; }) => ({ text: 'New ' + shell.profileName } as ISelectOptionItem)) || []; +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 62094770b0e..ba98b4a40d0 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -95,6 +95,12 @@ export interface ITerminalConfiguration { osx: string[]; windows: string[]; }; + profiles: { + linux: Map; + osx: Map; + windows: Map; + }; + detectWslProfiles: boolean; altClickMovesCursor: boolean; macOptionIsMeta: boolean; macOptionClickForcesSelection: boolean; @@ -217,16 +223,35 @@ export interface IBeforeProcessDataEvent { data: string; } -export interface IShellDefinition { - label: string; +export interface ITerminalProfile { + profileName: string; path: string; + isWorkspaceProfile?: boolean; + args?: string | string[] | undefined; } -export interface IAvailableShellsRequest { - callback: (shells: IShellDefinition[]) => void; +export const enum ProfileSource { + 'Git Bash', + 'PowerShell' } +export interface ITerminalExecutable { + path: string | string[]; + args?: string | string[] | undefined; +} +export interface ITerminalProfileSource { + source: ProfileSource; +} + +export type ProfileName = string; + +export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSource | null; + +export interface IAvailableProfilesRequest { + callback: (shells: ITerminalProfile[]) => void; + quickLaunchOnly: boolean; +} export interface IDefaultShellAndArgsRequest { useAutomationShell: boolean; callback: (shell: string, args: string[] | string | undefined) => void; @@ -262,7 +287,7 @@ export interface ITerminalProcessManager extends IDisposable { dispose(immediate?: boolean): void; detachFromProcess(): void; createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise; - relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise; + relaunch(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean, reset: boolean): Promise; write(data: string): void; setDimensions(cols: number, rows: number): Promise; setDimensions(cols: number, rows: number, sync: false): Promise; @@ -322,10 +347,6 @@ export interface IStartExtensionTerminalRequest { callback: (error: ITerminalLaunchError | undefined) => void; } -export interface IAvailableShellsRequest { - callback: (shells: IShellDefinition[]) => void; -} - export interface IDefaultShellAndArgsRequest { useAutomationShell: boolean; callback: (shell: string, args: string[] | string | undefined) => void; @@ -377,7 +398,7 @@ export const enum TERMINAL_COMMAND_ID { FOCUS_NEXT = 'workbench.action.terminal.focusNext', FOCUS_PREVIOUS = 'workbench.action.terminal.focusPrevious', PASTE = 'workbench.action.terminal.paste', - SELECT_DEFAULT_SHELL = 'workbench.action.terminal.selectDefaultShell', + SELECT_DEFAULT_PROFILE = 'workbench.action.terminal.selectDefaultShell', RUN_SELECTED_TEXT = 'workbench.action.terminal.runSelectedText', RUN_ACTIVE_FILE = 'workbench.action.terminal.runActiveFile', SWITCH_TERMINAL = 'workbench.action.terminal.switchTerminal', diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index b4ddfa8a6d4..30678944711 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -80,6 +80,62 @@ export const terminalConfiguration: IConfigurationNode = { ], default: [] }, + 'terminal.integrated.profiles.windows': { + markdownDescription: localize({ + key: 'terminal.integrated.profiles.windows', + comment: ['{0}, {1}, {2}, and {3} are the `source`, `path`, `profileName`, and optional `args` settings keys'] + }, + "The windows shell profiles to select from when creating a new terminal via the terminal dropdown. Set to null to exclude them, use the {0} property to use the default detected configuration. Or, set the {1}, {2}, and optional {3}", '`source`', '`path`', '`profileName`', '`args`.'), + type: 'object', + default: { + 'PowerShell': { + source: 'PowerShell' + }, + 'Git Bash': { + source: 'Git Bash' + }, + 'Command Prompt': { + path: + [ + '${env:windir}\\Sysnative\\cmd.exe', + '${env:windir}\\System32\\cmd.exe' + ], + args: [] + }, + 'Windows PowerShell': { + comment: 'note that this will not be included in the quickSelect drop down if another version of powershell is installed', + path: + [ + '${env:windir}\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', + '${env:windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + ], + args: [] + }, + 'WSL': null, + 'Cygwin': null + }, + }, + 'terminal.integrated.profiles.osx': { + markdownDescription: localize({ + key: 'terminal.integrated.profile.osx', + comment: ['{0}, {1}, and {2} are the `path`, `profileName`, and optional `args` settings keys'] + }, + "The osx shell profiles to select from when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0}, {1}, and optional {2}", '`path`', '`profileName`', '`args`.'), + type: 'object' + }, + 'terminal.integrated.profiles.linux': { + markdownDescription: localize({ + key: 'terminal.integrated.profile.linux', + comment: ['{0}, {1}, and {2} are the `path`, `profileName`, and optional `args` settings keys'] + }, + "The linux shell profiles to select from when creating a new terminal via the terminal dropdown. When set, these will override the default detected profiles. They are comprised of a {0}, {1}, and optional {2}", '`path`', '`profileName`', '`args`.'), + type: 'object' + }, + 'terminal.integrated.detectWslProfiles': { + description: localize('terminal.integrated.detectWslProfiles', 'Controls whether or not WSL distros are detected as default profiles'), + type: 'boolean', + default: true + }, 'terminal.integrated.macOptionIsMeta': { description: localize('terminal.integrated.macOptionIsMeta', "Controls whether to treat the option key as the meta key in the terminal on macOS."), type: 'boolean', diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts index 9e67808966f..18f764df2bc 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts @@ -7,7 +7,6 @@ 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 { 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'; @@ -18,6 +17,7 @@ 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'; +import { linuxDistro } from 'vs/workbench/contrib/terminal/node/terminal'; export class TerminalNativeContribution extends Disposable implements IWorkbenchContribution { public _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index bd840ca32d6..b9aaae778bc 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -7,7 +7,7 @@ import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; -import { env as processEnv } from 'vs/base/common/process'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; @@ -98,7 +98,7 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe } public async createProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean, shouldPersist: boolean): Promise { - const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, env, processEnv as IProcessEnvironment, windowsEnableConpty, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName()); + const id = await this._localPtyService.createProcess(shellLaunchConfig, cwd, cols, rows, env, process.env, windowsEnableConpty, shouldPersist, this._getWorkspaceId(), this._getWorkspaceName()); const pty = this._instantiationService.createInstance(LocalPty, id, shouldPersist); this._ptys.set(id, pty); return pty; diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index 2a135cb5230..51a10b87cf7 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -6,11 +6,7 @@ import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; import { SymlinkSupport } from 'vs/base/node/pfs'; -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'; -import { IShellDefinition, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; +import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; let detectedDistro = LinuxDistro.Unknown; if (platform.isLinux) { @@ -30,96 +26,3 @@ if (platform.isLinux) { } export const linuxDistro = detectedDistro; - -export function detectAvailableShells(): Promise { - return platform.isWindows ? detectAvailableWindowsShells() : detectAvailableUnixShells(); -} - -async function detectAvailableWindowsShells(): Promise { - // Determine the correct System32 path. We want to point to Sysnative - // when the 32-bit version of VS Code is running on a 64-bit machine. - // The reason for this is because PowerShell's important PSReadline - // module doesn't work if this is not the case. See #27915. - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; - - let useWSLexe = false; - - if (getWindowsBuildNumber() >= 16299) { - useWSLexe = true; - } - - const expectedLocations: { [key: string]: string[] } = { - 'Command Prompt': [`${system32Path}\\cmd.exe`], - 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], - 'Git Bash': [ - `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, - ], - // See #75945 - // Cygwin: [ - // `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, - // `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` - // ] - }; - - // Add all of the different kinds of PowerShells - for await (const pwshExe of enumeratePowerShellInstallations()) { - expectedLocations[pwshExe.displayName] = [pwshExe.exePath]; - } - - const promises: Promise[] = []; - Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key]))); - const shells = await Promise.all(promises); - return coalesce(shells); -} - -async function detectAvailableUnixShells(): Promise { - const contents = await fs.promises.readFile('/etc/shells', 'utf8'); - const shells = contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0); - return shells.map(e => { - return { - label: basename(e), - path: e - }; - }); -} - -async function validateShellPaths(label: string, potentialPaths: string[]): Promise { - if (potentialPaths.length === 0) { - return Promise.resolve(undefined); - } - const current = potentialPaths.shift()!; - if (current! === '') { - return validateShellPaths(label, potentialPaths); - } - try { - const result = await fs.promises.stat(normalize(current)); - if (result.isFile() || result.isSymbolicLink()) { - return { - label, - path: current - }; - } - } catch (e) { - // Also try using lstat as some symbolic links on Windows - // throw 'permission denied' using 'stat' but don't throw - // using 'lstat' - try { - const result = await fs.promises.lstat(normalize(current)); - if (result.isFile() || result.isSymbolicLink()) { - return { - label, - path: current - }; - } - } - catch (e) { - // noop - } - } - return validateShellPaths(label, potentialPaths); -} diff --git a/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts b/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts new file mode 100644 index 00000000000..b8226f79f46 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts @@ -0,0 +1,279 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as platform from 'vs/base/common/platform'; +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'; +import { ITerminalConfiguration, ITerminalExecutable, ITerminalProfile, ITerminalProfileSource } from 'vs/workbench/contrib/terminal/common/terminal'; +import * as cp from 'child_process'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ITestTerminalConfig } from 'vs/workbench/contrib/terminal/test/node/terminalProfiles.test'; +import { ILogService } from 'vs/platform/log/common/log'; + +export interface IStatProvider { + stat(path: string): boolean, + lstat(path: string): boolean +} + +interface IPotentialTerminalProfile { + profileName: string, + paths: string[], + args?: string[] +} + +export function detectAvailableProfiles(quickLaunchOnly: boolean, logService?: ILogService, config?: ITerminalConfiguration | ITestTerminalConfig, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder, statProvider?: IStatProvider): Promise { + return platform.isWindows ? detectAvailableWindowsProfiles(quickLaunchOnly, logService, config?.detectWslProfiles, config?.profiles?.windows, variableResolver, workspaceFolder, statProvider) : detectAvailableUnixProfiles(); +} + +async function detectAvailableWindowsProfiles(quickLaunchOnly: boolean, logService?: ILogService, detectWslProfiles?: boolean, configProfiles?: any, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder, statProvider?: IStatProvider): Promise { + // Determine the correct System32 path. We want to point to Sysnative + // when the 32-bit version of VS Code is running on a 64-bit machine. + // The reason for this is because PowerShell's important PSReadline + // module doesn't work if this is not the case. See #27915. + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; + + let useWSLexe = false; + + if (getWindowsBuildNumber() >= 16299) { + useWSLexe = true; + } + + let expectedProfiles: IPotentialTerminalProfile[] = [ + { + profileName: 'Command Prompt', + paths: [`${system32Path}\\cmd.exe`] + }, + { + profileName: 'Git Bash', + paths: [ + `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, + ], + args: ['--login'] + }, + { + profileName: 'Cygwin', + paths: [ + `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, + `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` + ], + args: ['-l'] + }, + ... await getWslProfiles(`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`, detectWslProfiles, logService), + ... await getPowershellProfiles() + ]; + + const promises: Promise[] = []; + expectedProfiles.forEach(profile => promises.push(validateProfilePaths(profile.profileName, profile.paths, statProvider, profile.args))); + const profiles = await Promise.all(promises); + + let detectedProfiles = coalesce(profiles); + + if (!quickLaunchOnly) { + return detectedProfiles; + } + let validProfiles: ITerminalProfile[] = []; + + if (detectedProfiles && configProfiles) { + for (const [profileKey, value] of Object.entries(configProfiles)) { + if (value !== null) { + if ((value as ITerminalExecutable).path) { + let profile; + const customProfile = (value as ITerminalExecutable); + if (Array.isArray(customProfile.path)) { + let resolvedPaths: string[] = []; + for (const p of customProfile.path) { + const resolved = variableResolver?.resolve(workspaceFolder, p); + if (resolved) { + resolvedPaths.push(resolved); + } else if (statProvider) { + // used by tests + resolvedPaths.push(p); + } else { + logService?.trace(`Could not resolve path ${p} in workspace folder ${workspaceFolder}`); + } + } + profile = detectedProfiles?.find(profile => resolvedPaths.includes(profile.path)); + if (!profile) { + logService?.trace(`Could not detect path ${JSON.stringify(resolvedPaths)}`); + } + } else { + let resolved = variableResolver?.resolve(workspaceFolder, customProfile.path); + if (resolved) { + profile = detectedProfiles?.find(profile => profile.path === resolved); + } else if (statProvider) { + // used by tests + resolved = customProfile.path; + } else { + logService?.trace(`Could not resolve path ${customProfile.path} in workspace folder ${workspaceFolder}`); + } + if (!profile) { + logService?.trace(`Could not detect path ${resolved}`); + } + } + if (profile) { + if (customProfile.args) { + validProfiles?.push({ profileName: profileKey, path: profile.path, args: customProfile.args }); + } else { + validProfiles?.push({ profileName: profileKey, path: profile.path }); + } + } + } else if ((value as ITerminalProfileSource).source) { + // source + let sourceKey = (value as ITerminalProfileSource).source; + const profile = detectedProfiles?.find(profile => profile.profileName === sourceKey.toString()); + if (profile) { + validProfiles?.push({ profileName: profileKey, path: profile.path, args: profile.args }); + } else { + logService?.trace(`No source with key ${sourceKey}`); + } + } else { + logService?.trace(`Entry in terminal.profiles.windows is not of type ITerminalExecutable or Source`, profileKey, value); + } + } + } + } else { + logService?.trace(`No detected profiles ${JSON.stringify(detectedProfiles)} or ${JSON.stringify(configProfiles)}`); + } + + // only show the windows powershell profile if no other powershell profile exists + if (validProfiles.find(p => p.path.endsWith('pwsh.exe'))) { + validProfiles = validProfiles.filter(p => p.profileName !== 'Windows PowerShell'); + } + return validProfiles; +} + +async function getPowershellProfiles(): Promise { + let profiles: IPotentialTerminalProfile[] = []; + // Add all of the different kinds of PowerShells + for await (const pwshExe of enumeratePowerShellInstallations()) { + profiles.push({ profileName: pwshExe.displayName, paths: [pwshExe.exePath] }); + } + return profiles; +} + +async function getWslProfiles(wslPath: string, detectWslProfiles?: boolean, logService?: ILogService): Promise { + let profiles: IPotentialTerminalProfile[] = []; + if (detectWslProfiles) { + const distroOutput = await new Promise(r => cp.exec('wsl.exe -l', (err, stdout) => err ? logService?.trace('problem occurred when getting wsl distros', err) : r(stdout))); + if (distroOutput) { + let regex = new RegExp(/[\r?\n]/); + let distroNames = Buffer.from(distroOutput).toString('utf8').split(regex).filter(t => t.trim().length > 0 && t !== ''); + // don't need the Windows Subsystem for Linux Distributions header + distroNames.shift(); + for (const distroName of distroNames) { + let s = ''; + let counter = 0; + for (const c of Array.from(distroName)) { + if (counter % 2 === 1) { + // every other character is junk / a rectangle + s += c; + } + counter++; + } + if (s.endsWith('(Default)')) { + // Ubuntu (Default) -> Ubuntu bc (Default) won't work + s = s.substring(0, s.length - 10); + } + + // docker-desktop-data is used by docker-desktop to store container images and isn't a valid profile type + if (s !== '' && s !== 'docker-desktop-data') { + let profile = { profileName: `${s} (WSL)`, paths: [wslPath], args: [`-d`, `${s}`] }; + profiles.push(profile); + } + } + return profiles; + } + } + return []; +} + +async function detectAvailableUnixProfiles(): Promise { + const contents = await fs.promises.readFile('/etc/shells', 'utf8'); + const profiles = contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0); + return profiles.map(e => { + return { + profileName: basename(e), + path: e + }; + }); +} + +async function validateProfilePaths(label: string, potentialPaths: string[], statProvider?: IStatProvider, args?: string[]): Promise { + if (potentialPaths.length === 0) { + return Promise.resolve(undefined); + } + const current = potentialPaths.shift()!; + if (current! === '') { + return validateProfilePaths(label, potentialPaths, statProvider, args); + } + if (statProvider) { + if (statProvider.stat(current)) { + if (args) { + return { + profileName: label, + path: current, + args: args + }; + } else { + return { + profileName: label, + path: current + }; + } + } + } else { + try { + const result = await fs.promises.stat(normalize(current)); + if (result.isFile() || result.isSymbolicLink()) { + if (args) { + return { + profileName: label, + path: current, + args: args + }; + } else { + return { + profileName: label, + path: current + }; + } + } + } catch (e) { + // Also try using lstat as some symbolic links on Windows + // throw 'permission denied' using 'stat' but don't throw + // using 'lstat' + try { + const result = await fs.promises.lstat(normalize(current)); + if (result.isFile() || result.isSymbolicLink()) { + if (args) { + return { + profileName: label, + path: current, + args: args + }; + } else { + return { + profileName: label, + path: current + }; + } + } + } + catch (e) { + // noop + } + } + } + return validateProfilePaths(label, potentialPaths, statProvider, args); +} diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts new file mode 100644 index 00000000000..de60306a5e2 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { isWindows } from 'vs/base/common/platform'; +import { detectAvailableProfiles, IStatProvider } from 'vs/workbench/contrib/terminal/node/terminalProfiles'; + +export interface ITestTerminalConfig { + profiles: { + windows: { + [key: string]: { path?: string[], source?: string } + } + }, + detectWslProfiles: boolean +} + +suite('Workbench - TerminalProfiles', () => { + suite('detectAvailableProfiles', () => { + if (isWindows) { + suite('detectAvailableWindowsProfiles', async () => { + // test('should detect cmd prompt', async () => { + // const _paths = ['C:\\WINDOWS\\System32\\cmd.exe']; + // let config: ITestTerminalConfig = { + // profiles: { + // windows: { + // 'Command Prompt': { path: _paths } + // } + // }, + // detectWslProfiles: false + // }; + // const profiles = await detectAvailableProfiles(true, undefined, config, undefined, undefined, createStatProvider(_paths)); + // const expected = [{ profileName: 'Command Prompt', path: _paths[0] }]; + // assert.deepStrictEqual(expected, profiles); + // }); + test('should detect Git Bash and provide login args', async () => { + const _paths = [`C:\\Program Files\\Git\\bin\\bash.exe`]; + let config = { + profiles: { + windows: { + 'Git Bash': { + source: 'Git Bash' + }, + }, + }, + detectWslProfiles: false + }; + const profiles = await detectAvailableProfiles(true, undefined, config, undefined, undefined, createStatProvider(_paths)); + const expected = [{ profileName: 'Git Bash', path: _paths[0], args: ['--login'] }]; + assert.deepStrictEqual(profiles, expected); + }); + }); + } + }); +}); +function createStatProvider(expectedPaths: string[]): IStatProvider { + const provider = { + stat(path: string) { + return expectedPaths.includes(path); + }, + lstat(path: string) { + return expectedPaths.includes(path); + } + }; + return provider; +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts index 95f56b63a8f..3c5c4557e39 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts @@ -10,11 +10,15 @@ import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService'; export * as icons from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons'; @@ -128,3 +132,179 @@ registerAction2(class extends Action2 { } } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'gettingStarted.markTaskComplete', + title: localize('gettingStarted.markTaskComplete', "Mark Task Complete"), + category, + }); + } + + run(accessor: ServicesAccessor, arg: string) { + if (!arg) { return; } + const gettingStartedService = accessor.get(IGettingStartedService); + gettingStartedService.progressTask(arg); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'gettingStarted.markTaskIncomplete', + title: localize('gettingStarted.markTaskInomplete', "Mark Task Incomplete"), + category, + }); + } + + run(accessor: ServicesAccessor, arg: string) { + if (!arg) { return; } + const gettingStartedService = accessor.get(IGettingStartedService); + gettingStartedService.deprogressTask(arg); + } +}); + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.welcomePage.hiddenCategories': { + 'scope': ConfigurationScope.APPLICATION, + 'type': 'array', + 'items': { type: 'string' }, + 'default': [], + 'description': localize('welcomePage.hiddenCategories', "Hide categories of the welcome page's getting started section that are not relevant to you.") + }, + } + }); + +ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'walkthroughs', + jsonSchema: { + doNotSuggest: true, + description: localize('walkthroughs', "Contribute collections of tasks to help users with your extension. Experimental, available in VS Code Insiders only."), + type: 'array', + items: { + type: 'object', + required: ['id', 'title', 'description', 'tasks'], + defaultSnippets: [{ body: { 'id': '$1', 'title': '$2', 'description': '$3', 'tasks': [] } }], + properties: { + id: { + type: 'string', + description: localize('walkthroughs.id', "Unique identifier for this walkthrough."), + }, + title: { + type: 'string', + description: localize('walkthroughs.title', "Title of walkthrough.") + }, + description: { + type: 'string', + description: localize('walkthroughs.description', "Description of walkthrough.") + }, + when: { + type: 'string', + description: localize('walkthroughs.when', "Context key expression to control the visibility of this walkthrough.") + }, + tasks: { + type: 'array', + description: localize('walkthroughs.tasks', "Tasks to complete as part of this walkthrough."), + items: { + type: 'object', + required: ['id', 'title', 'description', 'button', 'media'], + defaultSnippets: [{ + body: { + 'id': '$1', 'title': '$2', 'description': '$3', + 'button': { 'title': '$4', 'command': '$5' }, + 'doneOn': { 'command': '$5' }, + 'media': { 'path': '$6', 'altText': '$7' } + } + }], + properties: { + id: { + type: 'string', + description: localize('walkthroughs.tasks.id', "Unique identifier for this task. This is used to keep track of which tasks have been completed."), + }, + title: { + type: 'string', + description: localize('walkthroughs.tasks.title', "Title of task.") + }, + description: { + type: 'string', + description: localize('walkthroughs.tasks.description', "Description of task.") + }, + button: { + description: localize('walkthroughs.tasks.button', "The task's button, which can either link to an external resource or run a command"), + oneOf: [ + { + type: 'object', + required: ['title', 'command'], + defaultSnippets: [{ 'body': { 'title': '$1', 'command': '$2' } }], + properties: { + title: { + type: 'string', + description: localize('walkthroughs.tasks.button.title', "Title of button.") + }, + command: { + type: 'string', + description: localize('walkthroughs.tasks.button.command', "Command to run when button is clicked.") + } + } + }, + { + type: 'object', + required: ['title', 'link'], + defaultSnippets: [{ 'body': { 'title': '$1', 'link': '$2' } }], + properties: { + title: { + type: 'string', + description: localize('walkthroughs.tasks.button.title', "Title of button.") + }, + link: { + type: 'string', + description: localize('walkthroughs.tasks.button.link', "Link to open when button is clicked. Opening this link will mark the task completed.") + } + } + } + ] + }, + media: { + type: 'object', + required: ['path', 'altText'], + description: localize('walkthroughs.tasks.media', "Image to show alongside this task."), + defaultSnippets: [{ 'body': { 'altText': '$1', 'path': '$2' } }], + properties: { + path: { + description: localize('walkthroughs.tasks.media.path', "Path to an image, relative to extension directory."), + type: 'string', + }, + altText: { + type: 'string', + description: localize('walkthroughs.tasks.media.altText', "Alternate text to display when the image cannot be loaded or in screen readers.") + } + } + }, + doneOn: { + description: localize('walkthroughs.tasks.doneOn', "Signal to mark task as complete."), + type: 'object', + required: ['command'], + defaultSnippets: [{ 'body': { command: '$1' } }], + properties: { + 'command': { + description: localize('walkthroughs.tasks.oneOn.command', "Mark task done when the specified command is executed."), + type: 'string' + } + }, + }, + when: { + type: 'string', + description: localize('walkthroughs.tasks.when', "Context key expression to control the visibility of this task.") + } + } + } + } + } + } + } +}); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index 536e913305f..698b148e147 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -18,6 +18,7 @@ width: 100%; user-select: initial; -webkit-user-select: initial; + outline: none; } .monaco-workbench .part.editor > .content .gettingStartedContainer img { @@ -84,21 +85,31 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories { display: flex; flex-direction: column; + align-items: center; + justify-content: center; overflow: hidden; padding: 20px; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-slide-container { + width: 90%; + max-width: 1200px; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .gap { flex: 150px 0 1000 } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .header { display: flex; - align-items: center; justify-content: center; flex-direction: column; } +.monaco-workbench .part.editor > .content .gettingStartedContainer.height-constrained .gettingStartedSlide.categories .header { + display: none; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .category-title { margin-bottom: 4px; font-weight: 500; @@ -111,13 +122,12 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view { display: flex; - justify-content: space-evenly; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .categories-left, .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .categories-right { - flex: 1; - max-width: 400px; + flex: 1 1 0; + overflow: hidden; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view ul { @@ -129,6 +139,9 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view li { list-style: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .categories-split-view .path { @@ -183,6 +196,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category { padding-right: 46px; + position: relative; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon { @@ -191,8 +205,24 @@ font-size: 20px; } -.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category img.category-icon { +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon.hide-category-button { + position: absolute; + right: -6px; + font-size: 12px; + padding-right: 0; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category img.category-icon { margin-right: 10px; + margin-left: 10px; + padding-right: 8px; + max-width: 20px; + max-height: 20px; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category img.category-icon { + margin-right: 10px; + margin-left: 10px; max-width: 32px; max-height: 32px; } @@ -234,6 +264,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task { display: flex; width: 100%; + margin: 4px 0; overflow: hidden; transition: height .1s linear; } @@ -270,6 +301,15 @@ align-items: center; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .shortcut-message { + opacity: 0.8; + font-size: 8pt; +} + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .shortcut-message .keybinding { + font-weight: bold; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next { margin-left: auto; margin-right: 10px; @@ -374,14 +414,6 @@ text-align: center; } -/* .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide h1 { - font-size: 32px; - font-weight: normal; - border-bottom: none; - margin: 0; - padding: 0; -} */ - .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide.detail h2 { font-weight: normal; line-height: 26px; @@ -415,6 +447,9 @@ background: transparent; margin: 2px; cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; } .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link:hover { @@ -424,3 +459,9 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .showOnStartup { text-align: center; } + +.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .showOnStartup label { + position: relative; + top: -2px; + left: 5px; +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index 37a6eccf707..c49b38f242c 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -9,7 +9,7 @@ import { IInstantiationService, optional } from 'vs/platform/instantiation/commo import { EditorInput, EditorOptions, IEditorInputFactory, IEditorOpenContext } from 'vs/workbench/common/editor'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { assertIsDefined } from 'vs/base/common/types'; -import { $, addDisposableListener, reset } from 'vs/base/browser/dom'; +import { $, addDisposableListener, Dimension, reset } from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IProductService } from 'vs/platform/product/common/productService'; import { IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService'; @@ -39,10 +39,11 @@ import { splitName } from 'vs/base/common/labels'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { coalesce } from 'vs/base/common/arrays'; import { isMacintosh } from 'vs/base/common/platform'; - const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; +const hiddenEntriesConfigurationKey = 'workbench.welcomePage.hiddenCategories'; + export const gettingStartedInputTypeId = 'workbench.editors.gettingStartedInput'; export const inGettingStartedContext = new RawContextKey('inGettingStarted', false); @@ -113,6 +114,7 @@ export class GettingStartedPage extends EditorPane { private tasExperimentService?: ITASExperimentService; private previousSelection?: string; private recentlyOpened: Promise; + private selectedTaskElement?: HTMLDivElement; constructor( @ICommandService private readonly commandService: ICommandService, @@ -134,7 +136,13 @@ export class GettingStartedPage extends EditorPane { super(GettingStartedPage.ID, telemetryService, themeService, storageService); - this.container = $('.gettingStartedContainer'); + this.container = $('.gettingStartedContainer', + { + role: 'document', + tabindex: 0, + 'aria-label': localize('gettingStartedLabel', "Getting Started. Overview of how to get up to speed with your editor.") + }); + this.tasExperimentService = tasExperimentService; this.contextService = this._register(contextService.createScoped(this.container)); @@ -167,8 +175,8 @@ export class GettingStartedPage extends EditorPane { } else { badgeelement.parentElement?.setAttribute('aria-checked', 'false'); - badgeelement.classList.add(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); badgeelement.classList.remove('complete', ...ThemeIcon.asClassNameArray(gettingStartedCheckedCodicon)); + badgeelement.classList.add(...ThemeIcon.asClassNameArray(gettingStartedUncheckedCodicon)); } }); } @@ -211,7 +219,7 @@ export class GettingStartedPage extends EditorPane { break; } case 'openFolder': { - this.commandService.executeCommand(isMacintosh ? 'workbench.action.openFileFolder' : 'workbench.action.openFolder'); + this.commandService.executeCommand(isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder'); break; } case 'selectCategory': { @@ -224,9 +232,29 @@ export class GettingStartedPage extends EditorPane { } break; } + case 'hideCategory': { + const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument); + if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); } + this.configurationService.updateValue(hiddenEntriesConfigurationKey, + [...(this.configurationService.getValue(hiddenEntriesConfigurationKey) ?? []), argument]); + element.parentElement?.remove(); + break; + } case 'selectTask': { this.selectTask(argument); - e.stopPropagation(); + break; + } + case 'toggleTaskCompletion': { + if (!this.currentCategory || this.currentCategory.content.type !== 'items') { + throw Error('cannot run task action for category of non items type' + this.currentCategory?.id); + } + + const taskToggle = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument)); + if (taskToggle.done) { + this.gettingStartedService.deprogressTask(argument); + } else { + this.gettingStartedService.progressTask(argument); + } break; } case 'runTaskAction': { @@ -242,7 +270,6 @@ export class GettingStartedPage extends EditorPane { } else { throw Error('Task ' + JSON.stringify(taskToRun) + ' does not have an associated action'); } - e.stopPropagation(); break; } default: { @@ -250,6 +277,7 @@ export class GettingStartedPage extends EditorPane { break; } } + e.stopPropagation(); })); } }); @@ -269,6 +297,7 @@ export class GettingStartedPage extends EditorPane { if (this.editorInput.selectedTask === id && contractIfAlreadySelected) { this.previousSelection = this.editorInput.selectedTask; this.editorInput.selectedTask = undefined; + this.selectedTaskElement = undefined; return; } taskElement.style.height = `${taskElement.scrollHeight}px`; @@ -276,6 +305,7 @@ export class GettingStartedPage extends EditorPane { throw Error('cannot expand task for category of non items type' + this.currentCategory?.id); } this.editorInput.selectedTask = id; + this.selectedTaskElement = taskElement; const taskToExpand = assertIsDefined(this.currentCategory.content.items.find(task => task.id === id)); mediaElement.setAttribute('alt', taskToExpand.media.altText); @@ -331,9 +361,7 @@ export class GettingStartedPage extends EditorPane { ); const gettingStartedPage = - $('.gettingStarted.welcomePageFocusElement', { - role: 'document', - 'aria-label': localize('gettingStartedLabel', "Getting Started. Overview of how to get up to speed with your editor.") + $('.gettingStarted', { }, $('.gettingStartedSlideCategory.gettingStartedSlide.categories'), tasksSlide @@ -350,30 +378,39 @@ export class GettingStartedPage extends EditorPane { } private async buildCategoriesSlide() { - const categoryElements = this.gettingStartedCategories.filter(entry => entry.content.type === 'items').map( - category => { - const categoryDescriptionElement = - category.content.type === 'items' ? - $('.category-description-container', {}, - $('h3.category-title', {}, category.title), - // $('.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-inner')))) - : - $('.category-description-container', {}, - $('h3.category-title', {}, category.title), - $('.category-description.description', { 'aria-label': category.description + ' ' + localize('pressEnterToSelect', "Press Enter to Select") }, category.description)); + const hiddenCategories = new Set(this.configurationService.getValue(hiddenEntriesConfigurationKey) ?? []); + const categoryElements = this.gettingStartedCategories + .filter(entry => entry.content.type === 'items') + .filter(entry => !hiddenCategories.has(entry.id)) + .map( + category => { + const categoryDescriptionElement = + category.content.type === 'items' ? + $('.category-description-container', {}, + $('h3.category-title', {}, category.title), + // $('.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-inner')))) + : + $('.category-description-container', {}, + $('h3.category-title', {}, category.title), + $('.category-description.description', { 'aria-label': category.description + ' ' + localize('pressEnterToSelect', "Press Enter to Select") }, category.description)); - return $('button.getting-started-category', - { - 'x-dispatch': 'selectCategory:' + category.id, - 'role': 'listitem', - }, - this.iconWidgetFor(category), - categoryDescriptionElement); - }); + return $('button.getting-started-category', + { + 'x-dispatch': 'selectCategory:' + category.id, + 'role': 'listitem', + 'title': category.description + }, + this.iconWidgetFor(category), + $('a.codicon.codicon-close.hide-category-button', { + 'x-dispatch': 'hideCategory:' + category.id, + 'title': localize('close', "Hide"), + }), + categoryDescriptionElement); + }); const categoryScrollContainer = $('.getting-started-categories-scrolling-container'); const categoriesContainer = $('ul.getting-started-categories-container', { 'role': 'list' }); @@ -401,24 +438,22 @@ export class GettingStartedPage extends EditorPane { const categoriesSlide = assertIsDefined(this.container.querySelector('.gettingStartedSlideCategory') as HTMLElement); reset(categoriesSlide, - $('.gap'), - $('.header', {}, - $('h1.product-name.caption', {}, localize('gettingStarted.vscode', "Visual Studio Code")), - $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved")), - ), - $('.categories-split-view', {}, - $('.gap'), - $('.categories-left', {}, - this.buildStartList(), - this.buildRecentlyOpenedList(), + $('.categories-slide-container', {}, + $('.header', {}, + $('h1.product-name.caption', {}, localize('gettingStarted.vscode', "Visual Studio Code")), + $('p.subtitle.description', {}, localize({ key: 'gettingStarted.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved")), ), - $('.categories-right', {}, - $('h2', {}, localize('gettingStarted', "Getting Started")), - this.categoriesScrollbar.getDomNode(), - ), - $('.gap'), + $('.categories-split-view', {}, + $('.categories-left', {}, + this.buildStartList(), + this.buildRecentlyOpenedList(), + ), + $('.categories-right', {}, + $('h2', {}, localize('gettingStarted', "Getting Started")), + this.categoriesScrollbar.getDomNode(), + ), + ) ), - $('.gap'), $('.footer', {}, $('p.showOnStartup', {}, showOnStartupCheckbox, @@ -513,7 +548,7 @@ export class GettingStartedPage extends EditorPane { } if (!workspaces.length) { this.container.classList.add('emptyRecent'); - ul.append($('.empty-recent', {}, 'You hve no recent folders,', $('button.button-link', {}, 'open a folder'), 'to start.')); + ul.append($('.empty-recent', {}, 'You have no recent folders,', $('button.button-link', { 'x-dispatch': 'openFolder' }, 'open a folder'), 'to start.')); return; } @@ -531,7 +566,7 @@ export class GettingStartedPage extends EditorPane { updateEntries(); this.registerDispatchListeners(); })); - }).then(undefined, onUnexpectedError); + }).then(() => this.registerDispatchListeners(), onUnexpectedError); return container; } @@ -542,10 +577,8 @@ export class GettingStartedPage extends EditorPane { if (entry.content.type === 'items') { return undefined; } - const li = $('li', { 'x-dispatch': 'selectCategory:' + entry.id }, this.iconWidgetFor(entry)); - const button = $('button.button-link'); - - button.innerText = entry.title; + const li = $('li', {}); + const button = $('button.button-link', { 'x-dispatch': 'selectCategory:' + entry.id }, this.iconWidgetFor(entry), $('span', {}, entry.title)); button.title = entry.description; li.appendChild(button); @@ -568,20 +601,17 @@ export class GettingStartedPage extends EditorPane { - layout() { + layout(size: Dimension) { this.categoriesScrollbar?.scanDomNode(); this.detailsScrollbar?.scanDomNode(); this.detailImageScrollbar?.scanDomNode(); + this.container.classList[size.height <= 685 ? 'add' : 'remove']('height-constrained'); - // 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); - }); + + if (this.selectedTaskElement) { + this.selectedTaskElement.style.height = ``; // unset or the scrollHeight will just be the old height + this.selectedTaskElement.style.height = `${this.selectedTaskElement.scrollHeight}px`; + } } private updateCategoryProgress() { @@ -597,8 +627,8 @@ export class GettingStartedPage extends EditorPane { bar.setAttribute('aria-valuemin', '0'); bar.setAttribute('aria-valuenow', '' + numDone); bar.setAttribute('aria-valuemax', '' + numTotal); - - bar.style.width = `${(numDone / numTotal) * 100}%`; + const progress = Math.max((numDone / numTotal) * 100, 3); + bar.style.width = `${progress}%`; if (numTotal === numDone) { bar.title = `All items complete!`; @@ -649,34 +679,50 @@ export class GettingStartedPage extends EditorPane { $('.category-description.description', {}, category.description)))); const categoryElements = category.content.items.map( - (task, i, arr) => $('button.getting-started-task', - { - 'x-dispatch': 'selectTask:' + task.id, - 'data-task-id': task.id, - 'aria-expanded': 'false', - 'aria-checked': '' + task.done, - 'role': 'listitem', - }, - $('.codicon' + (task.done ? '.complete' + ThemeIcon.asCSSSelector(gettingStartedCheckedCodicon) : ThemeIcon.asCSSSelector(gettingStartedUncheckedCodicon)), { 'data-done-task-id': task.id }), - $('.task-description-container', {}, + (task, i, arr) => { + + const codicon = $('.codicon' + (task.done ? '.complete' + ThemeIcon.asCSSSelector(gettingStartedCheckedCodicon) : ThemeIcon.asCSSSelector(gettingStartedUncheckedCodicon)), + { + 'data-done-task-id': task.id, + 'x-dispatch': 'toggleTaskCompletion:' + task.id, + }); + + const taskActions = $('.actions', {}, + $('button.emphasis.getting-started-task-action', + { 'x-dispatch': 'runTaskAction:' + task.id }, + task.button.title), + ...( + arr[i + 1] + ? [$('button.task-next.button-link', { 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")),] + : nextCategory + ? [$('button.task-next.button-link', { 'x-dispatch': 'selectCategory:' + nextCategory.id }, localize('nextPage', "Next Page")),] + : [] + )); + + + const taskDescription = $('.task-description-container', {}, $('h3.task-title', {}, task.title), $('.task-description.description', {}, task.description), $('.image-description', { 'aria-label': localize('imageShowing', "Image showing {0}", task.media.altText) }), - $('.actions', {}, - ...( - task.button - ? [$('button.emphasis.getting-started-task-action', { 'x-dispatch': 'runTaskAction:' + task.id }, - task.button.title + (task.button.command ? this.getKeybindingLabel(task.button.command) : '') - )] - : []), - ...( - arr[i + 1] - ? [$('button.task-next.button-link', { 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")),] - : nextCategory - ? [$('button.task-next.button-link', { 'x-dispatch': 'selectCategory:' + nextCategory.id }, localize('nextPage', "Next Page")),] - : [] - )) - ))); + taskActions, + ); + + const keybindingLabel = (task.button.command && this.getKeybindingLabel(task.button.command)); + if (keybindingLabel) { + taskDescription.appendChild($('span.shortcut-message', {}, 'Pro Tip: Use keyboard shortcut ', $('span.keybinding', {}, keybindingLabel))); + } + + return $('button.getting-started-task', + { + 'x-dispatch': 'selectTask:' + task.id, + 'data-task-id': task.id, + 'aria-expanded': 'false', + 'aria-checked': '' + task.done, + 'role': 'listitem', + }, + codicon, + taskDescription); + }); const detailContainer = $('.getting-started-detail-container', { 'role': 'list' }); if (this.detailsScrollbar) { this.detailsScrollbar.getDomNode().remove(); this.detailsScrollbar.dispose(); } @@ -700,7 +746,7 @@ export class GettingStartedPage extends EditorPane { private getKeybindingLabel(command: string) { const binding = this.keybindingService.lookupKeybinding(command); if (!binding) { return ''; } - else { return ` (${binding.getLabel()})`; } + else { return binding.getLabel() ?? ''; } } private async scrollPrev() { @@ -751,15 +797,6 @@ export class GettingStartedPage extends EditorPane { } } - private focusFirstUncompletedCategory() { - let toFocus!: HTMLElement; - this.container.querySelectorAll('.category-progress').forEach(progress => { - const progressAmount = assertIsDefined(progress.querySelector('.progress-bar-inner') as HTMLDivElement).style.width; - if (!toFocus && progressAmount !== '100%') { toFocus = assertIsDefined(progress.parentElement?.parentElement); } - }); - (toFocus ?? assertIsDefined(this.container.querySelector('button.getting-started-category')) as HTMLButtonElement)?.focus(); - } - private setSlide(toEnable: 'details' | 'categories') { const slideManager = assertIsDefined(this.container.querySelector('.gettingStarted')); if (toEnable === 'categories') { @@ -767,7 +804,7 @@ export class GettingStartedPage extends EditorPane { slideManager.classList.add('showCategories'); this.container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = true); this.container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = false); - this.focusFirstUncompletedCategory(); + this.container.focus(); } else { slideManager.classList.add('showDetails'); slideManager.classList.remove('showCategories'); @@ -857,7 +894,7 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer a { color: ${link}; }`); collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link { color: ${link}; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link * { color: ${link}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer .button-link .scroll-button { color: ${link}; }`); } const activeLink = theme.getColor(textLinkActiveForeground); if (activeLink) { @@ -870,7 +907,8 @@ registerThemingParticipant((theme, collector) => { } const border = theme.getColor(contrastBorder); if (border) { - collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button { border-color: ${border}; border: 1px solid; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button { border: 1px solid ${border}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .gettingStartedContainer button.button-link { border: inherit; }`); } const activeBorder = theme.getColor(activeContrastBorder); if (activeBorder) { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 5f06b8c2b02..2f7b9e16adb 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -17,7 +17,7 @@ import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/brows import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { localize } from 'vs/nls'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -39,7 +39,6 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { CancellationToken } from 'vs/base/common/cancellation'; import { domEvent } from 'vs/base/browser/event'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -67,7 +66,6 @@ export class WalkThroughPart extends EditorPane { private lastFocus: HTMLElement | undefined; private size: Dimension | undefined; private editorMemento: IEditorMemento; - private tasExperimentService: ITASExperimentService | undefined; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -82,12 +80,10 @@ export class WalkThroughPart extends EditorPane { @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @optional(ITASExperimentService) tasExperimentService: ITASExperimentService, ) { super(WalkThroughPart.ID, telemetryService, themeService, storageService); this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService); this.editorMemento = this.getEditorMemento(editorGroupService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY); - this.tasExperimentService = tasExperimentService; } createEditor(container: HTMLElement): void { @@ -194,15 +190,6 @@ export class WalkThroughPart extends EditorPane { this.notificationService.info(localize('walkThrough.gitNotFound', "It looks like Git is not installed on your system.")); return; } - if (uri.scheme === 'command' && uri.path === 'workbench.action.files.newUntitledFile') { - Promise.all([ - this.tasExperimentService?.getTreatment('newuntitledmode'), - this.openerService.open(this.addFrom(uri)), - ]).then(([newUntitledMode]) => { - return newUntitledMode && this.openerService.open(this.addFrom(URI.parse('command:workbench.action.editor.changeLanguageMode'))); - }); - return; - } this.openerService.open(this.addFrom(uri)); } diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index 26f0d614f7a..8b285d2e546 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -90,38 +90,49 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben this.telemetryService.publicLog2('workspaceTrustRequested', { modal: this.requestModel.trustRequest.modal, workspaceId: this.workspaceContextService.getWorkspace().id, - extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.requiresWorkspaceTrust).map(ext => ext.identifier.value) + extensions: (await this.extensionService.getExtensions()).filter(ext => !!ext.workspaceTrust).map(ext => ext.identifier.value) }); if (this.requestModel.trustRequest.modal) { + // Message + const defaultMessage = localize('immediateTrustRequestMessage', "A feature you are trying to use may be a security risk if you do not trust the source of the files or folders you currently have open."); + const message = this.requestModel.trustRequest.message ?? defaultMessage; + + // Buttons + const buttons = this.requestModel.trustRequest.buttons ?? [ + { label: localize('grantWorkspaceTrustButton', "Continue"), type: 'ContinueWithTrust' }, + { label: localize('manageWorkspaceTrustButton', "Learn More"), type: 'Manage' } + ]; + // Add Cancel button if not provided + if (!buttons.some(b => b.type === 'Cancel')) { + buttons.push({ label: localize('cancelWorkspaceTrustButton', "Cancel"), type: 'Cancel' }); + } + + // Dialog const result = await this.dialogService.show( Severity.Warning, localize('immediateTrustRequestTitle', "Do you trust the files in this folder?"), - [ - localize('grantWorkspaceTrustButton', "Trust"), - localize('denyWorkspaceTrustButton', "Don't Trust"), - localize('manageWorkspaceTrustButton', "Manage"), - localize('cancelWorkspaceTrustButton', "Cancel"), - ], + buttons.map(b => b.label), { - cancelId: 3, - detail: localize('immediateTrustRequestDetail', "A feature you are trying to use may be a security risk if you do not trust the source of the files or folders you currently have open.\n\nYou should only trust this workspace if you trust its source. Otherwise, features will be enabled that may compromise your device or personal information."), + cancelId: buttons.findIndex(b => b.type === 'Cancel'), + detail: localize('immediateTrustRequestDetail', "{0}\n\nYou should only trust this workspace if you trust its source. Otherwise, features will be enabled that may compromise your device or personal information.", message), } ); - switch (result.choice) { - case 0: // Trust + // Dialog result + switch (buttons[result.choice].type) { + case 'ContinueWithTrust': this.requestModel.completeRequest(WorkspaceTrustState.Trusted); break; - case 1: // Don't Trust - this.requestModel.completeRequest(WorkspaceTrustState.Untrusted); - break; - case 2: // Manage + case 'ContinueWithoutTrust': this.requestModel.completeRequest(undefined); + break; + case 'Manage': + this.requestModel.cancelRequest(); await this.commandService.executeCommand('workbench.trust.manage'); break; - default: // Cancel - this.requestModel.completeRequest(undefined); + case 'Cancel': + this.requestModel.cancelRequest(); break; } } diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css index 2ee8dc601c1..5711d0dcb3c 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.css @@ -8,22 +8,23 @@ content: '\eb53'; } -.workspace-trust-editor.settings-editor { +.workspace-trust-editor { max-width: 1000px; padding-top: 11px; padding-left: 15px; padding-right: 15px; margin: auto; + height: calc(100% - 11px); } -.workspace-trust-editor.settings-editor > .workspace-trust-header { +.workspace-trust-editor > .workspace-trust-header { padding: 14px; display: flex; flex-direction: column; align-items: center; } -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-title { +.workspace-trust-editor .workspace-trust-header .workspace-trust-title { font-size: 24px; font-weight: 600; line-height: 32px; @@ -31,27 +32,27 @@ display: flex; } -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon { +.workspace-trust-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon { font-size: 32px; margin-right: 8px; } -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon.codicon-workspace-trusted-icon { +.workspace-trust-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon.codicon-workspace-trusted-icon { color: var(--workspace-trust-state-trusted-color) !important; } -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon.codicon-workspace-untrusted-icon { +.workspace-trust-editor .workspace-trust-header .workspace-trust-title .workspace-trust-title-icon.codicon-workspace-untrusted-icon { color: var(--workspace-trust-state-untrusted-color) !important; } -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-description { +.workspace-trust-editor .workspace-trust-header .workspace-trust-description { cursor: default; user-select: text; max-width: 500px; } /** Buttons Container */ -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-buttons-row { +.workspace-trust-editor .workspace-trust-header .workspace-trust-buttons-row { display: flex; align-items: center; justify-content: flex-end; @@ -61,12 +62,12 @@ } /** Buttons */ -.workspace-trust-editor.settings-editor .workspace-trust-header .workspace-trust-buttons-row .workspace-trust-buttons { +.workspace-trust-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 { +.workspace-trust-editor .workspace-trust-header .workspace-trust-buttons-row .workspace-trust-buttons .monaco-button { width: fit-content; width: -moz-fit-content; padding: 5px 10px; @@ -76,71 +77,129 @@ outline-offset: 2px !important; } -.workspace-trust-editor.settings-editor .workspace-trust-section-title { +.workspace-trust-editor .workspace-trust-section-title { font-weight: 600; font-size: 18px; padding-bottom: 5px; } +.workspace-trust-editor .workspace-trust-editor-body { + height: 100%; +} + /** Features List */ -.workspace-trust-editor.settings-editor > .workspace-trust-features { +.workspace-trust-editor .workspace-trust-features { padding: 14px; cursor: default; user-select: text; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extensions-list { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extensions-list { padding-bottom: 10px; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-title { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-title { font-size: 16px; font-weight: 600; line-height: 32px; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-description { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-description { padding-bottom: 15px; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry { + +.workspace-trust-editor .extensions-grid-view { + display: flex; + flex-wrap: wrap; +} + +.workspace-trust-editor .extensions-grid-view > .extension-container { + width: 310px; + margin: 0 10px 20px 0; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item { + padding-left: 0; + cursor: default; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details { + display: flex; + flex-direction: column; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details .header > .name { + cursor: pointer; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .footer, +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .header-container { + flex-grow: 0; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .version, +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .ratings, +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .header-container > .header > .install-count { + display: none; +} + +.workspace-trust-editor .extensions-grid-view > .extension-container:focus > .extension-list-item > .details .header > .name, +.workspace-trust-editor .extensions-grid-view > .extension-container:focus > .extension-list-item > .details .header > .name:hover { + text-decoration: underline; +} + +.workspace-trust-editor .extensions-grid-view .extension-list-item > .details > .description { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + flex-grow: 1; + white-space: break-spaces; +} + + +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-entry { display: flex; margin-bottom: 10px; align-items: center; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-text { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-text { display: flex; flex-direction: column; margin-left: 5px; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-icon, -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-icon .icon { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-icon, +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-icon .icon { height: 42px; } -.workspace-trust-editor.settings-editor > .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-name { +.workspace-trust-editor .workspace-trust-features .workspace-trust-extension-list-entry .workspace-trust-extension-name { font-weight: 600; } /** Settings */ -.workspace-trust-editor.settings-editor .workspace-trust-settings .workspace-trust-section-title { +.workspace-trust-editor .workspace-trust-settings .workspace-trust-section-title { padding: 14px; } -.workspace-trust-editor.settings-editor > .settings-body > .settings-tree-container .shadow.top { +.workspace-trust-editor .settings-editor .settings-body { + margin-top: 0; +} + +.workspace-trust-editor .settings-editor .settings-body .settings-tree-container .shadow.top { left: initial; margin-left: initial; max-width: initial; } -.workspace-trust-editor.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { +.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 > .setting-list-valueInput { +.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 index 8c3f6eb20a4..853d136deb6 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -12,6 +12,7 @@ import { Codicon, registerCodicon } from 'vs/base/common/codicons'; import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { parseLinkedText } from 'vs/base/common/linkedText'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { isArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -26,6 +27,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace 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 { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { getInstalledExtensions, IExtensionStatus } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; @@ -38,6 +40,10 @@ const untrustedIcon = registerCodicon('workspace-untrusted-icon', Codicon.worksp const trustedIcon = registerCodicon('workspace-trusted-icon', Codicon.workspaceTrusted); const unknownIcon = registerCodicon('workspace-unknown-icon', Codicon.workspaceUnknown); +class WorkspaceTrustExtensionDelegate extends Delegate { + getHeight() { return super.getHeight() + 36; } +} + export class WorkspaceTrustEditor extends EditorPane { static readonly ID: string = 'workbench.editor.workspaceTrust'; private rootElement!: HTMLElement; @@ -50,6 +56,8 @@ export class WorkspaceTrustEditor extends EditorPane { private headerDescription!: HTMLElement; private headerButtons!: HTMLElement; + private bodyScrollBar!: DomScrollableElement; + // Affected Features Section private affectedFeaturesContainer!: HTMLElement; private extensionsContainer!: HTMLElement; @@ -73,11 +81,20 @@ export class WorkspaceTrustEditor extends EditorPane { ) { super(WorkspaceTrustEditor.ID, telemetryService, themeService, storageService); } protected createEditor(parent: HTMLElement): void { - this.rootElement = append(parent, $('.workspace-trust-editor.settings-editor', { tabindex: '-1' })); + this.rootElement = append(parent, $('.workspace-trust-editor', { tabindex: '-1' })); this.createHeaderElement(this.rootElement); - this.createAffectedFeaturesElement(this.rootElement); - this.createConfigurationElement(this.rootElement); + + const scrollableContent = $('.workspace-trust-editor-body'); + this.bodyScrollBar = this._register(new DomScrollableElement(scrollableContent, { + horizontal: ScrollbarVisibility.Hidden, + vertical: ScrollbarVisibility.Visible, + })); + + append(this.rootElement, this.bodyScrollBar.getDomNode()); + + this.createAffectedFeaturesElement(scrollableContent); + this.createConfigurationElement(scrollableContent); this._register(attachStylerCallback(this.themeService, { trustedForegroundColor, untrustedForegroundColor }, colors => { this.rootElement.style.setProperty('--workspace-trust-state-trusted-color', colors.trustedForegroundColor?.toString() || ''); @@ -226,10 +243,12 @@ export class WorkspaceTrustEditor extends EditorPane { // Configuration Tree this.workspaceTrustSettingsTreeModel.update(model.dataModel.getTrustStateInfo()); this.trustSettingsTree.setChildren(null, Iterable.map(this.workspaceTrustSettingsTreeModel.settings, s => { return { element: s }; })); + + this.bodyScrollBar.scanDomNode(); } private async getExtensionsByTrustRequirement(extensions: IExtensionStatus[], trustRequirement: ExtensionWorkspaceTrustRequirement): Promise { - const filtered = extensions.filter(ext => ext.local.manifest.requiresWorkspaceTrust === trustRequirement); + const filtered = extensions.filter(ext => ext.local.manifest.workspaceTrust?.required === trustRequirement); const ids = filtered.map(ext => ext.identifier.id); return getExtensions(ids, this.extensionWorkbenchService); @@ -251,7 +270,7 @@ export class WorkspaceTrustEditor extends EditorPane { const scrollableContent = new DomScrollableElement(content, { useShadows: false }); append(parent, scrollableContent.getDomNode()); - const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content); + const extensionsGridView = this.instantiationService.createInstance(ExtensionsGridView, content, new WorkspaceTrustExtensionDelegate()); extensionsGridView.setExtensions(extensions); scrollableContent.scanDomNode(); @@ -272,12 +291,14 @@ export class WorkspaceTrustEditor extends EditorPane { } private createConfigurationElement(parent: HTMLElement): void { - this.configurationContainer = append(parent, $('.workspace-trust-settings.settings-body')); + this.configurationContainer = append(parent, $('.workspace-trust-settings.settings-editor')); const titleContainer = append(this.configurationContainer, $('.workspace-trust-section-title')); titleContainer.innerText = localize('configurationSectionTitle', "Configure All Workspaces"); - const workspaceTrustTreeContainer = append(this.configurationContainer, $('.workspace-trust-settings-tree-container.settings-tree-container')); + const settingsBody = append(this.configurationContainer, $('.workspace-trust-settings-body.settings-body')); + + const workspaceTrustTreeContainer = append(settingsBody, $('.workspace-trust-settings-tree-container.settings-tree-container')); const renderer = this.instantiationService.createInstance(WorkspaceTrustSettingArrayRenderer,); this.trustSettingsTree = this._register(this.instantiationService.createInstance(WorkspaceTrustTree, @@ -319,13 +340,14 @@ export class WorkspaceTrustEditor extends EditorPane { return; } - const listHeight = dimension.height - this.configurationContainer.offsetTop; - this.configurationContainer.style.height = `${listHeight}`; - - this.trustSettingsTree.layout(listHeight, dimension.width); + this.trustSettingsTree.layout(dimension.height, dimension.width); this.layoutParticipants.forEach(participant => { participant.layout(); }); + + this.bodyScrollBar.getDomNode().style.height = `calc(100% - ${this.headerContainer.clientHeight}px)`; + + this.bodyScrollBar.scanDomNode(); } } diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts index 1abfc3cb2d7..4da885f50fc 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustTree.ts @@ -280,6 +280,7 @@ export class WorkspaceTrustTree extends WorkbenchObjectTree .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}; }`); + 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}; }`); + 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}; }`); + 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}; }`); + 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}; }`); + 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}; }`); + 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}; }`); + 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}; }`); + 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} }`); + 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}; }`); + 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} }`); + collector.addRule(`.workspace-trust-editor .workspace-trust-settings .workspace-trust-settings-tree-container .setting-item-contents .setting-item-markdown a:focus { outline-color: ${focusBorderColor} }`); } })); diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index d84a48c3071..ff859c92df1 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import * as os from 'os'; import { gracefulify } from 'graceful-fs'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; +import { INativeWorkbenchConfiguration, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { productService, SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; @@ -23,7 +21,7 @@ import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver class DesktopMain extends SharedDesktopMain { constructor(configuration: INativeWorkbenchConfiguration) { - super(configuration, new NativeWorkbenchEnvironmentService(configuration, productService)); + super(configuration, new NativeWorkbenchEnvironmentService(configuration, { homeDir: os.homedir(), tmpDir: os.tmpdir(), userDataDir: getUserDataPath(configuration) }, productService)); // Enable gracefulFs gracefulify(fs); @@ -37,10 +35,6 @@ class DesktopMain extends SharedDesktopMain { } } - protected createLogService(loggerService: LoggerChannelClient, mainProcessService: IMainProcessService): ILogService { - return this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService)); - } - protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void { // Local Files diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 1c85192b291..97771758f35 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -3,25 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { INativeWorkbenchConfiguration, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IFileService } from 'vs/platform/files/common/files'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { simpleFileSystemProvider, SimpleNativeWorkbenchEnvironmentService, SimpleLogService, simpleWorkspace } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; -import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; -import { SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; +import { simpleHomeDir, simpleFileSystemProvider, simpleWorkspaceDir, simpleTmpDir, simpleUserDataDir } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; +import { productService, SharedDesktopMain } from 'vs/workbench/electron-sandbox/shared.desktop.main'; class DesktopMain extends SharedDesktopMain { constructor(configuration: INativeWorkbenchConfiguration) { - super({ ...configuration, workspace: simpleWorkspace }, new SimpleNativeWorkbenchEnvironmentService(configuration)); - } - - protected createLogService(loggerService: LoggerChannelClient, mainProcessService: IMainProcessService): ILogService { - return new SimpleLogService(); // we can only use the real logger, once `IEnvironmentService#logFile` has a proper file:// based value (https://github.com/microsoft/vscode/issues/116829) + super({ ...configuration, workspace: { id: configuration.workspace?.id ?? '4064f6ec-cb38-4ad0-af64-ee6467e63c82', uri: simpleWorkspaceDir } }, new NativeWorkbenchEnvironmentService(configuration, { homeDir: simpleHomeDir.fsPath, tmpDir: simpleTmpDir.fsPath, userDataDir: simpleUserDataDir.fsPath }, productService)); } protected registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts index d7989c75cbb..406fefe55e5 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/menubarControl.ts @@ -106,6 +106,7 @@ export class NativeMenubarControl extends MenubarControl { private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) { let groups = menu.getActions(); + for (let group of groups) { const [, actions] = group; @@ -127,13 +128,16 @@ export class NativeMenubarControl extends MenubarControl { const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); this.populateMenuItems(menuToDispose, submenu, keybindings); - let menubarSubmenuItem: IMenubarMenuItemSubmenu = { - id: menuItem.id, - label: title, - submenu: submenu - }; + if (submenu.items.length > 0) { + let menubarSubmenuItem: IMenubarMenuItemSubmenu = { + id: menuItem.id, + label: title, + submenu: submenu + }; + + menuToPopulate.items.push(menubarSubmenuItem); + } - menuToPopulate.items.push(menubarSubmenuItem); menuToDispose.dispose(); } else { if (menuItem.id === 'workbench.action.openRecent') { diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index ff340f53ada..f5ab5d61502 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -3,21 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable code-no-standalone-editor */ /* eslint-disable code-import-patterns */ import { URI } from 'vs/base/common/uri'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { Event } from 'vs/base/common/event'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; -import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { isWindows } from 'vs/base/common/platform'; import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { ITunnelProvider, ITunnelService, RemoteTunnel, TunnelProviderFeatures } from 'vs/platform/remote/common/tunnel'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ITaskProvider, ITaskService, ITaskSummary, ProblemMatcherRunOptions, Task, TaskFilter, TaskTerminateResponse, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { Action } from 'vs/base/common/actions'; import { LinkedMap } from 'vs/base/common/map'; @@ -26,116 +23,26 @@ import { CustomTask, ContributedTask, InMemoryTask, TaskRunSource, ConfiguringTa import { TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; -import type { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; -import { Schemas } from 'vs/base/common/network'; import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ConsoleLogger, LogService } from 'vs/platform/log/common/log'; +import { SearchService } from 'vs/workbench/services/search/common/searchService'; +import { ISearchService } from 'vs/workbench/services/search/common/search'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; //#region Environment -const userDataDir = URI.file('/sandbox-user-data-dir'); - -export class SimpleNativeWorkbenchEnvironmentService implements INativeWorkbenchEnvironmentService { - - declare readonly _serviceBrand: undefined; - - constructor( - readonly configuration: INativeWorkbenchConfiguration - ) { } - - get userRoamingDataHome(): URI { return userDataDir.with({ scheme: Schemas.userData }); } - get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } - get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } - get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } - get globalStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'globalStorage'); } - get workspaceStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'workspaceStorage'); } - get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } - get logFile(): URI { return joinPath(this.userRoamingDataHome, 'window.log'); } - get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } - get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); } - get userDataSyncLogResource(): URI { return joinPath(this.userRoamingDataHome, 'syncLog'); } - get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'syncHome'); } - get userDataPath(): string { return userDataDir.fsPath; } - get tmpDir(): URI { return joinPath(this.userRoamingDataHome, 'tmp'); } - get logsPath(): string { return joinPath(this.userRoamingDataHome, 'logs').path; } - - sessionId = this.configuration.sessionId; - machineId = this.configuration.machineId; - remoteAuthority = this.configuration.remoteAuthority; - os = { release: 'unknown' }; - - options?: IWorkbenchConstructionOptions | undefined; - logExtensionHostCommunication?: boolean | undefined; - extensionEnabledProposedApi?: string[] | undefined; - webviewExternalEndpoint: string = undefined!; - webviewResourceRoot: string = undefined!; - webviewCspSource: string = undefined!; - skipReleaseNotes: boolean = undefined!; - keyboardLayoutResource: URI = undefined!; - sync: 'on' | 'off' | undefined; - debugExtensionHost: IExtensionHostDebugParams = undefined!; - debugRenderer = false; - isExtensionDevelopment: boolean = false; - disableExtensions: boolean | string[] = []; - extensionDevelopmentLocationURI?: URI[] | undefined; - extensionDevelopmentKind?: ExtensionKind[] | undefined; - extensionTestsLocationURI?: URI | undefined; - logLevel?: string | undefined; - - args: NativeParsedArgs = Object.create(null); - - execPath: string = undefined!; - appRoot: string = undefined!; - userHome: URI = undefined!; - appSettingsHome: URI = undefined!; - machineSettingsResource: URI = undefined!; - - log?: string | undefined; - extHostLogsPath: URI = undefined!; - - installSourcePath: string = undefined!; - - extensionsPath: string = undefined!; - extensionsDownloadPath: string = undefined!; - builtinExtensionsPath: string = undefined!; - - driverHandle?: string | undefined; - - crashReporterDirectory?: string | undefined; - crashReporterId?: string | undefined; - - nodeCachedDataDir?: string | undefined; - - verbose = false; - isBuilt = false; - - get telemetryLogResource(): URI { return joinPath(this.userRoamingDataHome, 'telemetry.log'); } - disableTelemetry = false; -} - -//#endregion - +export const simpleHomeDir = URI.file(isWindows ? '\\sandbox-home-dir' : '/sandbox-home-dir'); +export const simpleTmpDir = URI.file(isWindows ? '\\sandbox-tmp-dir' : '/sandbox-tmp-dir'); +export const simpleUserDataDir = URI.file(isWindows ? '\\sandbox-user-data-dir' : '/sandbox-user-data-dir'); //#region Workspace -export const simpleWorkspace: ISingleFolderWorkspaceIdentifier = { id: '4064f6ec-cb38-4ad0-af64-ee6467e63c82', uri: URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace') }; - -//#endregion - - -//#region Logger - -export class SimpleLogService extends LogService { - - constructor() { - super(new ConsoleLogger()); - } - -} +export const simpleWorkspaceDir = URI.file(isWindows ? '\\simpleWorkspace' : '/simpleWorkspace'); //#endregion @@ -146,14 +53,28 @@ class SimpleFileSystemProvider extends InMemoryFileSystemProvider { } export const simpleFileSystemProvider = new SimpleFileSystemProvider(); -simpleFileSystemProvider.mkdir(userDataDir); +simpleFileSystemProvider.mkdir(simpleHomeDir); +simpleFileSystemProvider.mkdir(simpleTmpDir); +simpleFileSystemProvider.mkdir(simpleUserDataDir); +simpleFileSystemProvider.mkdir(joinPath(simpleUserDataDir, 'User')); +simpleFileSystemProvider.writeFile(joinPath(simpleUserDataDir, 'User', 'settings.json'), VSBuffer.fromString(JSON.stringify({ + 'window.zoomLevel': 1, + 'workbench.colorTheme': 'Default Light+', + +}, undefined, '\t')).buffer, { create: true, overwrite: true, unlock: false }); +simpleFileSystemProvider.writeFile(joinPath(simpleUserDataDir, 'User', 'keybindings.json'), VSBuffer.fromString(JSON.stringify([ + { + 'key': 'f12', + 'command': 'workbench.action.toggleDevTools' + } +], undefined, '\t')).buffer, { create: true, overwrite: true, unlock: false }); function createWorkspaceFile(parent: string, name: string, content: string = ''): void { - simpleFileSystemProvider.writeFile(joinPath(simpleWorkspace.uri, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true, unlock: false }); + simpleFileSystemProvider.writeFile(joinPath(simpleWorkspaceDir, parent, name), VSBuffer.fromString(content).buffer, { create: true, overwrite: true, unlock: false }); } function createWorkspaceFolder(name: string): void { - simpleFileSystemProvider.mkdir(joinPath(simpleWorkspace.uri, name)); + simpleFileSystemProvider.mkdir(joinPath(simpleWorkspaceDir, name)); } createWorkspaceFolder(''); @@ -446,3 +367,24 @@ class SimpleTerminalInstanceService extends TerminalInstanceService { } registerSingleton(ITerminalInstanceService, SimpleTerminalInstanceService); //#endregion + + +//#region Search Service + +class SimpleSearchService extends SearchService { + constructor( + @IModelService modelService: IModelService, + @IEditorService editorService: IEditorService, + @ITelemetryService telemetryService: ITelemetryService, + @ILogService logService: ILogService, + @IExtensionService extensionService: IExtensionService, + @IFileService fileService: IFileService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + ) { + super(modelService, editorService, telemetryService, logService, extensionService, fileService, uriIdentityService); + } +} + +registerSingleton(ISearchService, SimpleSearchService); + +//#endregion diff --git a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts index 2c29e8720c2..f3180da9c8f 100644 --- a/src/vs/workbench/electron-sandbox/shared.desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/shared.desktop.main.ts @@ -45,6 +45,7 @@ import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/product/common/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService'; export const productService = { _serviceBrand: undefined, ...product }; @@ -131,7 +132,6 @@ export abstract class SharedDesktopMain extends Disposable { this._register(workbench.onShutdown(() => this.dispose())); } - protected abstract createLogService(loggerService: ILoggerService, mainProcessService: IMainProcessService): ILogService; protected abstract registerFileSystemProviders(fileService: IFileService, logService: ILogService, nativeHostService: INativeHostService): void; protected joinOpen(instantiationService: IInstantiationService): void { } @@ -167,7 +167,7 @@ export abstract class SharedDesktopMain extends Disposable { serviceCollection.set(ILoggerService, loggerService); // Log - const logService = this.createLogService(loggerService, mainProcessService); + const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, loggerService, mainProcessService, this.environmentService)); serviceCollection.set(ILogService, logService); // Remote diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 6238f5c5a21..0e56bad023f 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -21,6 +21,7 @@ import { setFullscreen, getZoomLevel } from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { env } from 'vs/base/common/process'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -55,7 +56,6 @@ import { IAddressProvider, IAddress } from 'vs/platform/remote/common/remoteAgen import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { AuthInfo } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; -import { env } from 'vs/base/common/process'; import { ILogService } from 'vs/platform/log/common/log'; export class NativeWindow extends Disposable { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 8bf1a1dd45f..bc5357496ed 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -3,31 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; +import { flatten } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { AuthenticationSession, AuthenticationSessionsChangeEvent, AuthenticationProviderInformation } from 'vs/editor/common/modes'; +import { isWeb } from 'vs/base/common/platform'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { isString } from 'vs/base/common/types'; +import { AuthenticationProviderInformation, AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes'; +import * as nls from 'vs/nls'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { isString } from 'vs/base/common/types'; -import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { flatten } from 'vs/base/common/arrays'; -import { isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Severity } from 'vs/platform/notification/common/notification'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { isWeb } from 'vs/base/common/platform'; export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; } @@ -37,7 +37,7 @@ export interface IAccountUsage { lastUsed: number; } -const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces']; +const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'ms-vscode.remotehub', 'ms-vscode.remotehub-insiders', 'github.codespaces']; export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] { const accountKey = `${providerId}-${accountName}-usages`; diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 7b979c03b49..106fb0a144f 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -19,13 +19,13 @@ import { Schemas } from 'vs/base/common/network'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { NativeBackupFileService } from 'vs/workbench/services/backup/electron-sandbox/backupFileService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { VSBuffer } from 'vs/base/common/buffer'; -import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration, TestEnvironmentPaths } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestProductService } from 'vs/workbench/test/browser/workbenchTestServices'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { insert } from 'vs/base/common/arrays'; @@ -35,7 +35,7 @@ import { isEqual } from 'vs/base/common/resources'; class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(testDir: string, backupPath: string) { - super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': testDir }, TestProductService); + super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': testDir }, TestEnvironmentPaths, TestProductService); } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 82204f97a80..76642f56cd5 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -37,7 +37,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import product from 'vs/platform/product/common/product'; import { Event } from 'vs/base/common/event'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; @@ -76,7 +75,7 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.userData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.userData, new NullLogService()))); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, { _serviceBrand: undefined, ...product }, new RemoteAuthorityResolverService(undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); diff --git a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts index a8c74b40005..5ff57578034 100644 --- a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts @@ -11,9 +11,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { env } from 'vs/base/common/process'; import { ILabelService } from 'vs/platform/label/common/label'; export class ConfigurationResolverService extends BaseConfigurationResolverService { @@ -34,7 +33,7 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi getExecPath: (): string | undefined => { return environmentService.execPath; } - }, process.env as IProcessEnvironment, editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService); + }, env, editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService); } } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 7771db8a123..37dc83af709 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -20,10 +20,10 @@ import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { TestEditorService, TestProductService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; -import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration, TestEnvironmentPaths } from 'vs/workbench/test/electron-browser/workbenchTestServices'; const mockLineNumber = 10; class TestEditorServiceWithActiveEditor extends TestEditorService { @@ -712,6 +712,6 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(public userEnv: platform.IProcessEnvironment) { - super({ ...TestWorkbenchConfiguration, userEnv }, TestProductService); + super({ ...TestWorkbenchConfiguration, userEnv }, TestEnvironmentPaths, TestProductService); } } diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts deleted file mode 100644 index 411502b6398..00000000000 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ /dev/null @@ -1,80 +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 { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { memoize } from 'vs/base/common/decorators'; -import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import { join } from 'vs/base/common/path'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IOSConfiguration } from 'vs/platform/windows/common/windows'; - -export class NativeWorkbenchEnvironmentService extends NativeEnvironmentService implements INativeWorkbenchEnvironmentService { - - @memoize - get machineId() { return this.configuration.machineId; } - - @memoize - get sessionId() { return this.configuration.sessionId; } - - @memoize - get remoteAuthority() { return this.configuration.remoteAuthority; } - - @memoize - get execPath() { return this.configuration.execPath; } - - @memoize - get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } - - @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } - - @memoize - get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } - - @memoize - get webviewExternalEndpoint(): string { - const baseEndpoint = 'https://{{uuid}}.vscode-webview-test.com/{{commit}}'; - - return baseEndpoint.replace('{{commit}}', this.productService.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); - } - - @memoize - get webviewResourceRoot(): string { return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`; } - - @memoize - get webviewCspSource(): string { return `${Schemas.vscodeWebviewResource}:`; } - - @memoize - get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } - - @memoize - get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } - - @memoize - get extensionEnabledProposedApi(): string[] | undefined { - if (Array.isArray(this.args['enable-proposed-api'])) { - return this.args['enable-proposed-api']; - } - - if ('enable-proposed-api' in this.args) { - return []; - } - - return undefined; - } - - get os(): IOSConfiguration { - return this.configuration.os; - } - - constructor( - readonly configuration: INativeWorkbenchConfiguration, - private readonly productService: IProductService - ) { - super(configuration); - } -} diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts index dde8584156b..bbfa2f51135 100644 --- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -7,6 +7,12 @@ import { IWorkbenchConfiguration, IWorkbenchEnvironmentService } from 'vs/workbe import { INativeWindowConfiguration, IOSConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { AbstractNativeEnvironmentService, INativeEnvironmentPaths } from 'vs/platform/environment/common/environmentService'; +import { memoize } from 'vs/base/common/decorators'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { join } from 'vs/base/common/path'; +import { IProductService } from 'vs/platform/product/common/productService'; export const INativeWorkbenchEnvironmentService = refineServiceDecorator(IEnvironmentService); @@ -38,3 +44,71 @@ export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmen */ readonly configuration: INativeWorkbenchConfiguration; } + +export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironmentService implements INativeWorkbenchEnvironmentService { + + @memoize + get machineId() { return this.configuration.machineId; } + + @memoize + get sessionId() { return this.configuration.sessionId; } + + @memoize + get remoteAuthority() { return this.configuration.remoteAuthority; } + + @memoize + get execPath() { return this.configuration.execPath; } + + @memoize + get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + + @memoize + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } + + @memoize + get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } + + @memoize + get webviewExternalEndpoint(): string { + const baseEndpoint = 'https://{{uuid}}.vscode-webview-test.com/{{commit}}'; + + return baseEndpoint.replace('{{commit}}', this.productService.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37'); + } + + @memoize + get webviewResourceRoot(): string { return `${Schemas.vscodeWebviewResource}://{{uuid}}/{{resource}}`; } + + @memoize + get webviewCspSource(): string { return `${Schemas.vscodeWebviewResource}:`; } + + @memoize + get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } + + @memoize + get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } + + @memoize + get extensionEnabledProposedApi(): string[] | undefined { + if (Array.isArray(this.args['enable-proposed-api'])) { + return this.args['enable-proposed-api']; + } + + if ('enable-proposed-api' in this.args) { + return []; + } + + return undefined; + } + + get os(): IOSConfiguration { + return this.configuration.os; + } + + constructor( + readonly configuration: INativeWorkbenchConfiguration, + paths: INativeEnvironmentPaths, + productService: IProductService + ) { + super(configuration, paths, productService); + } +} diff --git a/src/vs/workbench/services/experiment/common/experimentService.ts b/src/vs/workbench/services/experiment/common/experimentService.ts index 9bc5a2e025c..270c3aaa872 100644 --- a/src/vs/workbench/services/experiment/common/experimentService.ts +++ b/src/vs/workbench/services/experiment/common/experimentService.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryData } from 'vs/base/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import product from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export const ITASExperimentService = createDecorator('TASExperimentService'); @@ -43,7 +43,10 @@ class MementoKeyValueStorage implements IKeyValueStorage { class ExperimentServiceTelemetry implements IExperimentationTelemetry { private _lastAssignmentContext: string | undefined; - constructor(private telemetryService: ITelemetryService) { } + constructor( + private telemetryService: ITelemetryService, + private productService: IProductService + ) { } get assignmentContext(): string[] | undefined { return this._lastAssignmentContext?.split(';'); @@ -52,7 +55,7 @@ class ExperimentServiceTelemetry implements IExperimentationTelemetry { // __GDPR__COMMON__ "VSCode.ABExp.Features" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } // __GDPR__COMMON__ "abexp.assignmentcontext" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } setSharedProperty(name: string, value: string): void { - if (name === product.tasConfig?.assignmentContextTelemetryPropertyName) { + if (name === this.productService.tasConfig?.assignmentContextTelemetryPropertyName) { this._lastAssignmentContext = value; } @@ -190,9 +193,10 @@ export class ExperimentService implements ITASExperimentService { @ITelemetryService private telemetryService: ITelemetryService, @IStorageService private storageService: IStorageService, @IConfigurationService private configurationService: IConfigurationService, + @IProductService private productService: IProductService ) { - if (product.tasConfig && this.experimentsEnabled && this.telemetryService.isOptedIn) { + if (productService.tasConfig && this.experimentsEnabled && this.telemetryService.isOptedIn) { this.tasClient = this.setupTASClient(); } } @@ -244,20 +248,20 @@ export class ExperimentService implements ITASExperimentService { private async setupTASClient(): Promise { const startSetup = Date.now(); const telemetryInfo = await this.telemetryService.getTelemetryInfo(); - const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (product.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); + const targetPopulation = telemetryInfo.msftInternal ? TargetPopulation.Internal : (this.productService.quality === 'stable' ? TargetPopulation.Public : TargetPopulation.Insiders); const machineId = telemetryInfo.machineId; const filterProvider = new ExperimentServiceFilterProvider( - product.version, - product.nameLong, + this.productService.version, + this.productService.nameLong, machineId, targetPopulation ); const keyValueStorage = new MementoKeyValueStorage(new Memento(ExperimentService.MEMENTO_ID, this.storageService)); - this.telemetry = new ExperimentServiceTelemetry(this.telemetryService); + this.telemetry = new ExperimentServiceTelemetry(this.telemetryService, this.productService); - const tasConfig = product.tasConfig!; + const tasConfig = this.productService.tasConfig!; const tasClient = new (await import('tas-client-umd')).ExperimentationService({ filterProviders: [filterProvider], telemetry: this.telemetry, diff --git a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts index 9a78b2a5bc9..21fcbb0c107 100644 --- a/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts @@ -10,7 +10,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { getUriFromAmdModule } from 'vs/base/common/amd'; +import { FileAccess } from 'vs/base/common/network'; interface IScannedBuiltinExtension { extensionPath: string; @@ -70,7 +70,7 @@ export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScanne enableBuiltinExtensions = environmentService.remoteAuthority ? false : true; } if (enableBuiltinExtensions) { - return getUriFromAmdModule(require, '../../../../../../extensions'); + return FileAccess.asBrowserUri('../../../../../../extensions', require); } return undefined; } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index f1bf55fbe5e..fc289efd555 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -281,7 +281,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench private _isDisabledByTrustRequirement(extension: IExtension): boolean { const workspaceTrustState = this.workspaceTrustService.getWorkspaceTrustState(); - if (extension.manifest.requiresWorkspaceTrust === 'onStart') { + if (extension.manifest.workspaceTrust?.required === 'onStart') { if (workspaceTrustState !== WorkspaceTrustState.Trusted) { this._addToWorkspaceDisabledExtensionsByTrustRequirement(extension); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index c6a2489375b..835a2851834 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -361,7 +361,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench } protected async checkForWorkspaceTrust(manifest: IExtensionManifest): Promise { - if (manifest.requiresWorkspaceTrust === 'onStart') { + if (manifest.workspaceTrust?.required === 'onStart') { const trustState = await this.workspaceTrustService.requireWorkspaceTrust(); return trustState === WorkspaceTrustState.Trusted ? Promise.resolve() : Promise.reject(canceled()); } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index f08798dd813..9689215d3db 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -270,13 +270,13 @@ export function throwProposedApiError(extension: IExtensionDescription): never { } export function checkRequiresWorkspaceTrust(extension: IExtensionDescription): void { - if (!extension.requiresWorkspaceTrust) { + if (!extension.workspaceTrust?.required) { throwRequiresWorkspaceTrustError(extension); } } export function throwRequiresWorkspaceTrustError(extension: IExtensionDescription): void { - throw new Error(`[${extension.identifier.value}]: This API is only available when the "requiresWorkspaceTrust" is set to "onStart" or "onDemand" in the extension's package.json.`); + throw new Error(`[${extension.identifier.value}]: This API is only available when the "workspaceTrust.require" is set to "onStart" or "onDemand" in the extension's package.json.`); } export function toExtension(extensionDescription: IExtensionDescription): IExtension { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 920bfd4beb7..1d37bfff3da 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -72,11 +72,11 @@ export function connectProxyResolver( function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType) { const proxySetting = { config: configProvider.getConfiguration('http') - .get('proxySupport') || 'off' + .get<'override' | 'on' | 'off'>('proxySupport') || 'off' }; configProvider.onDidChangeConfiguration(e => { proxySetting.config = configProvider.getConfiguration('http') - .get('proxySupport') || 'off'; + .get<'override' | 'on' | 'off'>('proxySupport') || 'off'; }); const certSetting = { config: !!configProvider.getConfiguration('http') diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index 6a84a655b09..97df7c29ff1 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -54,7 +54,7 @@ export interface IFilesConfigurationService { readonly hotExitConfiguration: string | undefined; - preventSaveConflicts(resource: URI, language: string): boolean; + preventSaveConflicts(resource: URI, language?: string): boolean; } export class FilesConfigurationService extends Disposable implements IFilesConfigurationService { @@ -203,7 +203,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return this.currentHotExitConfig; } - preventSaveConflicts(resource: URI, language: string): boolean { + preventSaveConflicts(resource: URI, language?: string): boolean { return this.configurationService.getValue('files.saveConflictResolution', { resource, overrideIdentifier: language }) !== 'overwriteFileOnDisk'; } } diff --git a/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts b/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts index 0e231be7902..c1c1c46eaee 100644 --- a/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts +++ b/src/vs/workbench/services/gettingStarted/common/gettingStartedRegistry.ts @@ -7,11 +7,9 @@ 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', @@ -172,101 +170,3 @@ 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'], - defaultSnippets: [{ body: { 'id': '$1', 'title': '$2', 'description': '$3', 'button': { 'title': '$4' }, 'media': { 'path': '$5', 'altText': '$6' } } }], - 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'], - defaultSnippets: [{ 'body': { 'title': '$1', 'command': '$2' } }], - 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'], - defaultSnippets: [{ 'body': { 'title': '$1', 'link': '$2' } }], - 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."), - defaultSnippets: [{ 'body': { 'altText': '$1' } }], - 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', - defaultSnippets: [{ 'body': '$1' }], - }, - { - type: 'object', - defaultSnippets: [{ 'body': { 'hc': '$1', 'light': '$2', 'dark': '$3' } }], - 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 f9f180907a8..277520be3bc 100644 --- a/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts +++ b/src/vs/workbench/services/gettingStarted/common/gettingStartedService.ts @@ -19,8 +19,8 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio 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'; +import { IProductService } from 'vs/platform/product/common/productService'; export const IGettingStartedService = createDecorator('gettingStartedService'); @@ -50,6 +50,8 @@ export interface IGettingStartedService { getCategories(): IGettingStartedCategoryWithProgress[] progressByEvent(eventName: string): void; + progressTask(id: string): void; + deprogressTask(id: string): void; } export class GettingStartedService extends Disposable implements IGettingStartedService { @@ -78,6 +80,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted @IContextKeyService private readonly contextService: IContextKeyService, @IUserDataAutoSyncEnablementService readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService, @IExtensionService private readonly extensionService: IExtensionService, + @IProductService private readonly productService: IProductService ) { super(); @@ -137,18 +140,17 @@ export class GettingStartedService extends Disposable implements IGettingStarted 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}`; + if ((extension.contributes?.walkthroughs?.length) && this.productService.quality === 'stable') { + console.warn('Extension', extension.identifier.value, 'contributes welcome page content but this is a Stable build and extension contributions are only available in Insiders. The contributed content will be disregarded.'); + return; + } + extension.contributes?.walkthroughs?.forEach(section => { + const categoryID = extension.identifier.value + '#' + section.id; this.registry.registerCategory({ content: { type: 'items' }, - description: localize('extContrib', "Learn more about {0}!", extension.displayName ?? extension.name), - title: extension.displayName || extension.name, + description: section.description, + title: section.title, id: categoryID, icon: { type: 'image', @@ -156,22 +158,30 @@ export class GettingStartedService extends Disposable implements IGettingStarted ? FileAccess.asBrowserUri(joinPath(extension.extensionLocation, extension.icon)).toString(true) : DefaultIconPath }, - when: ContextKeyExpr.true(), + when: ContextKeyExpr.deserialize(section.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, - }); - }); - } + try { + + section.tasks.forEach((task, index) => + this.registry.registerTask({ + button: task.button, + description: task.description, + media: { type: 'image', altText: task.media.altText, path: convertPaths(task.media.path) }, + doneOn: task.doneOn?.command + ? { commandExecuted: task.doneOn.command } + : task.button.command + ? { commandExecuted: task.button.command } + : { eventFired: `linkOpened:${task.button.link}` }, + id: extension.identifier.value + '#' + task.id, + title: task.title, + when: ContextKeyExpr.deserialize(task.when) ?? ContextKeyExpr.true(), + category: categoryID, + order: index, + })); + } catch (e) { + console.error('Error registering walkthrough tasks for ', categoryID, e); + } + }); } } @@ -241,7 +251,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted }; } - private progressTask(id: string) { + progressTask(id: string) { const oldProgress = this.taskProgress[id]; if (!oldProgress || oldProgress.done !== true) { this.taskProgress[id] = { done: true }; @@ -251,6 +261,13 @@ export class GettingStartedService extends Disposable implements IGettingStarted } } + deprogressTask(id: string) { + delete this.taskProgress[id]; + this.memento.saveMemento(); + const task = this.registry.getTask(id); + this._onDidProgressTask.fire(this.getTaskProgress(task)); + } + private progressByCommand(command: string) { const listening = this.commandListeners.get(command) ?? []; listening.forEach(id => this.progressTask(id)); diff --git a/src/vs/workbench/services/ipc/electron-sandbox/sharedProcessService.ts b/src/vs/workbench/services/ipc/electron-sandbox/sharedProcessService.ts index 197011ac979..bd6fa5c8b00 100644 --- a/src/vs/workbench/services/ipc/electron-sandbox/sharedProcessService.ts +++ b/src/vs/workbench/services/ipc/electron-sandbox/sharedProcessService.ts @@ -42,7 +42,7 @@ export class SharedProcessService extends Disposable implements ISharedProcessSe // as a result. As such, make sure we await the `Restored` // phase before making a connection attempt, but also add a // timeout to be safe against possible deadlocks. - // TODO@bpasero revisit this when the shared process connection + // TODO@sandbox revisit this when the shared process connection // is more cruicial. await Promise.race([this.lifecycleService.when(LifecyclePhase.Restored), timeout(2000)]); diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 2947484574b..2f630ed449b 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -14,7 +14,7 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { platform } from 'vs/base/common/process'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; @@ -94,7 +94,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { hoverBackground: getColor(theme, listHoverBackground), hoverForeground: getColor(theme, listHoverForeground) }, - platform: process.platform, + platform: platform, applicationName: this.productService.applicationName }; return this.issueService.openProcessExplorer(data); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts index a66910e0c58..64967e8838c 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { promises } from 'fs'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { writeFile } from 'vs/base/node/pfs'; diff --git a/src/vs/workbench/services/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentFileSystemChannel.ts index 275b4f901af..f490fc8f67a 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentFileSystemChannel.ts @@ -3,53 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI, UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IStat, IWatchOptions, FileOpenOptions, IFileSystemProviderWithFileReadWriteCapability, FileWriteOptions, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileFolderCopyCapability, FileReadStreamOptions, IFileSystemProviderWithOpenReadWriteCloseCapability } from 'vs/platform/files/common/files'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { OperatingSystem } from 'vs/base/common/platform'; -import { newWriteableStream, ReadableStreamEvents, ReadableStreamEventPayload } from 'vs/base/common/stream'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { canceled } from 'vs/base/common/errors'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IPCFileSystemProvider } from 'vs/platform/files/common/ipcFileSystemProvider'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; -export interface IFileChangeDto { - resource: UriComponents; - type: FileChangeType; -} - -export class RemoteFileSystemProvider extends Disposable implements - IFileSystemProviderWithFileReadWriteCapability, - IFileSystemProviderWithOpenReadWriteCloseCapability, - IFileSystemProviderWithFileReadStreamCapability, - IFileSystemProviderWithFileFolderCopyCapability { - - private readonly session: string = generateUuid(); - private readonly channel: IChannel; - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChangeFile = this._onDidChange.event; - - private _onDidWatchErrorOccur = this._register(new Emitter()); - readonly onDidErrorOccur = this._onDidWatchErrorOccur.event; - - private readonly _onDidChangeCapabilities = this._register(new Emitter()); - readonly onDidChangeCapabilities = this._onDidChangeCapabilities.event; - - private _capabilities!: FileSystemProviderCapabilities; - get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } +export class RemoteFileSystemProvider extends IPCFileSystemProvider { constructor(remoteAgentService: IRemoteAgentService) { - super(); - - const connection = remoteAgentService.getConnection()!; - this.channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); + super(remoteAgentService.getConnection()!.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME)); // Initially assume case sensitivity until remote environment is resolved this.setCaseSensitive(true); @@ -57,155 +20,5 @@ export class RemoteFileSystemProvider extends Disposable implements const remoteAgentEnvironment = await remoteAgentService.getEnvironment(); this.setCaseSensitive(remoteAgentEnvironment?.os === OperatingSystem.Linux); })(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.channel.listen('filechange', [this.session])(eventsOrError => { - if (Array.isArray(eventsOrError)) { - const events = eventsOrError; - this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); - } else { - const error = eventsOrError; - this._onDidWatchErrorOccur.fire(error); - } - })); - } - - setCaseSensitive(isCaseSensitive: boolean) { - let capabilities = ( - FileSystemProviderCapabilities.FileReadWrite - | FileSystemProviderCapabilities.FileOpenReadWriteClose - | FileSystemProviderCapabilities.FileReadStream - | FileSystemProviderCapabilities.FileFolderCopy - | FileSystemProviderCapabilities.FileWriteUnlock - ); - - if (isCaseSensitive) { - capabilities |= FileSystemProviderCapabilities.PathCaseSensitive; - } - - this._capabilities = capabilities; - this._onDidChangeCapabilities.fire(undefined); - } - - // --- forwarding calls - - stat(resource: URI): Promise { - return this.channel.call('stat', [resource]); - } - - open(resource: URI, opts: FileOpenOptions): Promise { - return this.channel.call('open', [resource, opts]); - } - - close(fd: number): Promise { - return this.channel.call('close', [fd]); - } - - async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { - const [bytes, bytesRead]: [VSBuffer, number] = await this.channel.call('read', [fd, pos, length]); - - // copy back the data that was written into the buffer on the remote - // side. we need to do this because buffers are not referenced by - // pointer, but only by value and as such cannot be directly written - // to from the other process. - data.set(bytes.buffer.slice(0, bytesRead), offset); - - return bytesRead; - } - - async readFile(resource: URI): Promise { - const buff = await this.channel.call('readFile', [resource]); - - return buff.buffer; - } - - readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents { - const stream = newWriteableStream(data => VSBuffer.concat(data.map(data => VSBuffer.wrap(data))).buffer); - - // Reading as file stream goes through an event to the remote side - const listener = this.channel.listen>('readFileStream', [resource, opts])(dataOrErrorOrEnd => { - - // data - if (dataOrErrorOrEnd instanceof VSBuffer) { - stream.write(dataOrErrorOrEnd.buffer); - } - - // end or error - else { - if (dataOrErrorOrEnd === 'end') { - stream.end(); - } else { - - // Since we receive data through a IPC channel, it is likely - // that the error was not serialized, or only partially. To - // ensure our API use is correct, we convert the data to an - // error here to forward it properly. - let error = dataOrErrorOrEnd; - if (!(error instanceof Error)) { - error = new Error(toErrorMessage(error)); - } - - stream.error(error); - stream.end(); - } - - // Signal to the remote side that we no longer listen - listener.dispose(); - } - }); - - // Support cancellation - token.onCancellationRequested(() => { - - // Ensure to end the stream properly with an error - // to indicate the cancellation. - stream.error(canceled()); - stream.end(); - - // Ensure to dispose the listener upon cancellation. This will - // bubble through the remote side as event and allows to stop - // reading the file. - listener.dispose(); - }); - - return stream; - } - - write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { - return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]); - } - - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]); - } - - delete(resource: URI, opts: FileDeleteOptions): Promise { - return this.channel.call('delete', [resource, opts]); - } - - mkdir(resource: URI): Promise { - return this.channel.call('mkdir', [resource]); - } - - readdir(resource: URI): Promise<[string, FileType][]> { - return this.channel.call('readdir', [resource]); - } - - rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise { - return this.channel.call('rename', [resource, target, opts]); - } - - copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise { - return this.channel.call('copy', [resource, target, opts]); - } - - watch(resource: URI, opts: IWatchOptions): IDisposable { - const req = Math.random(); - this.channel.call('watch', [this.session, req, resource, opts]); - - return toDisposable(() => this.channel.call('unwatch', [this.session, req])); } } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index ff115023703..ca7b1c79ef2 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -154,7 +154,7 @@ interface PortAttributes extends Attributes { export class PortsAttributes extends Disposable { private static SETTING = 'remote.portsAttributes'; - private static DEFAULTS = 'remote.portsAttributes.defaults'; + private static DEFAULTS = 'remote.unconfiguredPortsAttributes'; private static RANGE = /^(\d+)\-(\d+)$/; private portsAttributes: PortAttributes[] = []; private defaultPortAttributes: Attributes | undefined; @@ -164,7 +164,7 @@ export class PortsAttributes extends Disposable { constructor(private readonly configurationService: IConfigurationService) { super(); this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(PortsAttributes.SETTING)) { + if (e.affectsConfiguration(PortsAttributes.SETTING) || e.affectsConfiguration(PortsAttributes.DEFAULTS)) { this.updateAttributes(); } })); @@ -343,6 +343,7 @@ export class TunnelModel extends Disposable { super(); this.configPortsAttributes = new PortsAttributes(configurationService); this.tunnelRestoreValue = this.getTunnelRestoreValue(); + this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this)); this.forwarded = new Map(); this.remoteTunnels = new Map(); this.tunnelService.tunnels.then(tunnels => { @@ -394,8 +395,6 @@ export class TunnelModel extends Disposable { this._onClosePort.fire(address); } })); - - this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this)); } private makeTunnelPrivacy(isPublic: boolean) { @@ -453,15 +452,15 @@ export class TunnelModel extends Disposable { async forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean, isPublic?: boolean, restore: boolean = true): Promise { const existingTunnel = mapHasAddressLocalhostOrAllInterfaces(this.forwarded, remote.host, remote.port); + const port = local !== undefined ? local : remote.port; + const attributes = (await this.getAttributes([port]))?.get(port); + if (!existingTunnel) { const authority = this.environmentService.remoteAuthority; const addressProvider: IAddressProvider | undefined = authority ? { getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; } } : undefined; - const port = local !== undefined ? local : remote.port; - const attributes = (await this.getAttributes([port]))?.get(port); - const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic); if (tunnel && tunnel.localAddress) { const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces(this._candidates ?? new Map(), remote.host, remote.port); @@ -469,7 +468,7 @@ export class TunnelModel extends Disposable { remoteHost: tunnel.tunnelRemoteHost, remotePort: tunnel.tunnelRemotePort, localPort: tunnel.tunnelLocalPort, - name: name ?? attributes?.label, + name: attributes?.label ?? name, closeable: true, localAddress: tunnel.localAddress, runningProcess: matchingCandidate?.detail, @@ -486,8 +485,8 @@ export class TunnelModel extends Disposable { return tunnel; } } else { - if (name) { - existingTunnel.name = name; + if (attributes?.label ?? name) { + existingTunnel.name = attributes?.label ?? name; } this._onForwardPort.fire(); return mapHasAddressLocalhostOrAllInterfaces(this.remoteTunnels, remote.host, remote.port); @@ -646,7 +645,7 @@ export class TunnelModel extends Disposable { const allProviderResults = await Promise.all(flatten(this.portAttributesProviders.map(provider => { return Array.from(pidToPortsMapping.entries()).map(entry => { const portGroup = entry[1]; - const matchingCandidate = matchingCandidates.get(portGroup[1]); + const matchingCandidate = matchingCandidates.get(portGroup[0]); return provider.providePortAttributes(portGroup, matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token); }); diff --git a/src/vs/workbench/services/search/electron-browser/searchService.ts b/src/vs/workbench/services/search/electron-browser/searchService.ts index 0084fafa827..7eb33c677ee 100644 --- a/src/vs/workbench/services/search/electron-browser/searchService.ts +++ b/src/vs/workbench/services/search/electron-browser/searchService.ts @@ -13,7 +13,7 @@ import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDebugParams } from 'vs/platform/environment/common/environment'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { parseSearchPort } from 'vs/platform/environment/node/environmentService'; +import { parseSearchPort } from 'vs/platform/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService, isFileMatch } from 'vs/workbench/services/search/common/search'; diff --git a/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts index ddeb47d9551..f5f4190f3dc 100644 --- a/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import * as path from 'vs/base/common/path'; @@ -12,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, ISearchEngine, ISearchEngineStats, ISearchEngineSuccess, ISearchProgressItem, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFileMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { IProgressCallback, SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { DiskSearch } from 'vs/workbench/services/search/electron-browser/searchService'; -import { flakySuite } from 'vs/base/test/node/testUtils'; +import { flakySuite, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; const TEST_FOLDER_QUERIES = [ { folder: URI.file(path.normalize('/some/where')) } diff --git a/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts index 11e5dd7885e..87da97374bb 100644 --- a/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/fileSearch.integrationTest.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { flakySuite } from 'vs/base/test/node/testUtils'; +import { flakySuite, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { IFileQuery, IFolderQuery, ISerializedSearchProgressItem, isProgressMessage, QueryType } from 'vs/workbench/services/search/common/search'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index 33a9868c1c3..2f95736afa7 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -5,13 +5,12 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import * as platform from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IFolderQuery, QueryType, IRawFileMatch } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { flakySuite } from 'vs/base/test/node/testUtils'; +import { flakySuite, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = URI.file(path.join(TEST_FIXTURES, 'examples')); diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 6b93dd11bf9..92f078f7521 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -5,13 +5,12 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode, ISerializedFileMatch } from 'vs/workbench/services/search/common/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; -import { flakySuite } from 'vs/base/test/node/testUtils'; +import { flakySuite, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts index 1c6e0dc124e..59e9af6b584 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties.ts @@ -27,9 +27,9 @@ export async function resolveWorkbenchCommonProperties( const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; // __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.version.shell'] = process.versions && process.versions['electron']; + result['common.version.shell'] = process.versions['electron']; // __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.version.renderer'] = process.versions && process.versions['chrome']; + result['common.version.renderer'] = process.versions['chrome']; // __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.firstSessionDate'] = firstSessionDate; // __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index dee93ec518f..1476ebc0921 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -373,7 +373,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason }))); modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(model))); - modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: model, reason }))); + modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model, reason }))); modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(model))); modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); diff --git a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts index 176ea48d83a..bcefbded729 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts @@ -14,9 +14,8 @@ import { rimraf, copy, exists } from 'vs/base/node/pfs'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; import { detectEncodingByBOM } from 'vs/workbench/services/textfile/test/node/encoding/encoding.test'; import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test'; diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index a8e124ceae1..1b1ab46198d 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -9,7 +9,7 @@ import * as encoding from 'vs/workbench/services/textfile/common/encoding'; import * as terminalEncoding from 'vs/base/node/terminalEncoding'; import * as streams from 'vs/base/common/stream'; import * as iconv from 'iconv-lite-umd'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer'; import { splitLines } from 'vs/base/common/strings'; diff --git a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts index 82a46bf6627..71132fb2367 100644 --- a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts +++ b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts @@ -15,7 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IStartupMetrics, AbstractTimerService, Writeable, ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { context, process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class TimerService extends AbstractTimerService { @@ -86,8 +86,8 @@ registerSingleton(ITimerService, TimerService); //#region cached data logic export function didUseCachedData(): boolean { - // TODO@bpasero TODO@jrieken need a different way to figure out if cached data was used - if (context.sandbox) { + // TODO@sandbox need a different way to figure out if cached data was used + if (process.sandboxed) { return true; } // We surely don't use cached data when we don't tell the loader to do so diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-sandbox/urlService.ts index 30f57f77363..79220df33de 100644 --- a/src/vs/workbench/services/url/electron-sandbox/urlService.ts +++ b/src/vs/workbench/services/url/electron-sandbox/urlService.ts @@ -27,9 +27,9 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO @IMainProcessService mainProcessService: IMainProcessService, @IOpenerService openerService: IOpenerService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IProductService private readonly productService: IProductService + @IProductService productService: IProductService ) { - super(); + super(productService); this.urlService = ProxyChannel.toService(mainProcessService.getChannel('url')); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts new file mode 100644 index 00000000000..8c361845bda --- /dev/null +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUserDataSyncResourceEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncResourceEnablementService as BaseUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class UserDataSyncResourceEnablementService extends BaseUserDataSyncResourceEnablementService implements IUserDataSyncResourceEnablementService { + + constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IStorageService storageService: IStorageService, + @ITelemetryService telemetryService: ITelemetryService, + ) { + super(storageService, telemetryService); + } + + getResourceSyncStateVersion(resource: SyncResource): string | undefined { + return resource === SyncResource.Extensions ? this.environmentService.options?.settingsSyncOptions?.extensionsSyncStateVersion : undefined; + } + +} + +registerSingleton(IUserDataSyncResourceEnablementService, UserDataSyncResourceEnablementService); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts new file mode 100644 index 00000000000..69f83c09634 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopy.ts @@ -0,0 +1,1211 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { Event, Emitter } from 'vs/base/common/event'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ETAG_DISABLED, FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, IFileStatWithMetadata, IFileStreamContent } from 'vs/platform/files/common/files'; +import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { TaskSequentializer, timeout } from 'vs/base/common/async'; +import { ILogService } from 'vs/platform/log/common/log'; +import { DefaultEndOfLine, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; +import { assertIsDefined } from 'vs/base/common/types'; +import { ITextFileEditorModel, ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; +import { isWindows } from 'vs/base/common/platform'; + +export interface IFileWorkingCopyModelFactory { + + /** + * Asks the file working copy delegate to create a model from the given + * content under the provided resource. The content may originate from + * different sources depending on context: + * - from a backup if that exists + * - from the underlying file resource + * - passed in from the caller + * + * @param resource the `URI` of the model + * @param contents the content of the model to create it + * @param token support for cancellation + */ + createModel(resource: URI, contents: VSBufferReadableStream, token: CancellationToken): Promise; +} + +/** + * The underlying model of a file working copy provides some + * methods for the file working copy to function. The model is + * typically only available after the working copy has been + * resolved via it's `resolve()` method. + */ +export interface IFileWorkingCopyModel { + + /** + * An even that fires whenever the content of the file working + * copy model changes. The file working copy will listen to these + * changes and mark the working copy as dirty whenever this event + * fires. + * + * Note: ONLY report changes to the model but not the underlying + * file. The file working copy is tracking changes to the file + * automatically. + */ + readonly onDidChangeContent: Event; + + /** + * An event emitted right before disposing the model. + */ + readonly onWillDispose: Event; + + /** + * Snapshots the model's current content for writing. This must include + * any changes that were made to the model that are in memory. + * + * @param token support for cancellation + */ + snapshot(token: CancellationToken): Promise; + + /** + * Updates the model with the provided contents. The implementation should + * behave in a similar fashion as `IFileWorkingCopyModelFactory#createModel` + * except that here the model already exists and just needs to update to + * the provided contents. + * + * @param the contents to use for the model + * @param token support for cancellation + */ + update(contents: VSBufferReadableStream, token: CancellationToken): Promise; + + /** + * TODO@bpasero should find a better name here maybe together + * with the pushStackElement concept since this is around + * undo/redo? + */ + getAlternativeVersionId(): number; + + /** + * Close the current undo-redo element. This offers a way + * to create an undo/redo stop point. + * + * This method may for example be called right before the + * save is triggered so that the user can always undo back + * to the state before saving. + * + * TODO@bpasero rename to beforeSave()? + */ + pushStackElement(): void; + + /** + * @deprecated do not invest to implement, will try to get this to + * work through the other `snapshot` method. + */ + _backupSnapshot(): ITextSnapshot | undefined; +} + +export interface IFileWorkingCopyModelContentChangedEvent { + + /** + * Flag that indicates that this event was generated while undoing. + */ + readonly isUndoing: boolean; + + /** + * Flag that indicates that this event was generated while redoing. + */ + readonly isRedoing: boolean; +} + +/** + * A file based `IWorkingCopy` is backed by a `URI` from a + * known file system provider. Given this assumption, a lot + * of functionality can be built on top, such as saving in + * a secure way to prevent data loss. + */ +export interface IFileWorkingCopy extends IWorkingCopy, Disposable { + + /** + * An event for when a file working copy was resolved. + */ + readonly onDidResolve: Event; + + /** + * An event for when a file working copy was saved successfully. + */ + readonly onDidSave: Event; + + /** + * An event indicating that a file working copy save operation failed. + */ + readonly onDidSaveError: Event; + + /** + * An event for when the file working copy was reverted. + */ + readonly onDidRevert: Event; + + /** + * An event for when the orphaned state of the file working copy changes. + */ + readonly onDidChangeOrphaned: Event; + + /** + * An event for when the file working copy has been disposed. + */ + readonly onDispose: Event; + + /** + * Provides access to the underlying model of this file + * based working copy. As long as the file working copy + * has not been resolved, the model is `undefined`. + */ + readonly model: T | undefined; + + /** + * Whether we have a resolved model or not. + */ + isResolved(): this is IResolvedFileWorkingCopy; + + /** + * Whether the file working copy has been disposed or not. + */ + isDisposed(): boolean; + + /** + * Resolves a file working copy. + */ + resolve(options?: IFileWorkingCopyResolveOptions): Promise>; +} + +export interface IResolvedFileWorkingCopy extends IFileWorkingCopy { + + /** + * A resolved file working copy has a resolved model `T`. + */ + readonly model: T; +} + +/** + * States the file working copy can be in. + */ +export const enum FileWorkingCopyState { + + /** + * A file working copy is saved. + */ + SAVED, + + /** + * A file working copy is dirty. + */ + DIRTY, + + /** + * A file working copy is currently being saved but + * this operation has not completed yet. + */ + PENDING_SAVE, + + /** + * A file working copy is in conflict mode when changes + * cannot be saved because the underlying file has changed. + * File working copies in conflict mode are always dirty. + */ + CONFLICT, + + /** + * A file working copy is in orphan state when the underlying + * file has been deleted. + */ + ORPHAN, + + /** + * Any error that happens during a save that is not causing + * the `FileWorkingCopyState.CONFLICT` state. + * File working copies in error mode are always dirty. + */ + ERROR +} + +/** + * Metadata associated with a file working copy backup. + */ +interface IFileWorkingCopyBackupMetaData { + mtime: number; + ctime: number; + size: number; + etag: string; + orphaned: boolean; +} + +export interface IFileWorkingCopySaveOptions extends ISaveOptions { + + /** + * Save the file working copy with an attempt to unlock it. + */ + writeUnlock?: boolean; + + /** + * Save the file working copy with elevated privileges. + * + * Note: This may not be supported in all environments. + */ + writeElevated?: boolean; + + /** + * Allows to write to a file working copy even if it has been + * modified on disk. This should only be triggered from an + * explicit user action. + */ + ignoreModifiedSince?: boolean; + + /** + * If set, will bubble up the file working copy save error to + * the caller instead of handling it. + */ + ignoreErrorHandler?: boolean; +} + +export interface IFileWorkingCopyResolveOptions { + + /** + * The contents to use for the file working copy if known. If not + * provided, the contents will be retrieved from the underlying + * resource or backup if present. + * + * If contents are provided, the file working copy will be marked + * as dirty right from the beginning. + */ + contents?: VSBufferReadableStream; + + /** + * Go to disk bypassing any cache of the file working copy if any. + */ + forceReadFromDisk?: boolean; +} + +export class FileWorkingCopy extends Disposable implements IFileWorkingCopy { + + readonly capabilities: WorkingCopyCapabilities = WorkingCopyCapabilities.None; + + private _model: T | undefined = undefined; + get model(): T | undefined { return this._model; } + + //#region events + + private readonly _onDidChangeContent = this._register(new Emitter()); + readonly onDidChangeContent = this._onDidChangeContent.event; + + private readonly _onDidResolve = this._register(new Emitter()); + readonly onDidResolve = this._onDidResolve.event; + + private readonly _onDidChangeDirty = this._register(new Emitter()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidSaveError = this._register(new Emitter()); + readonly onDidSaveError = this._onDidSaveError.event; + + private readonly _onDidSave = this._register(new Emitter()); + readonly onDidSave = this._onDidSave.event; + + private readonly _onDidRevert = this._register(new Emitter()); + readonly onDidRevert = this._onDidRevert.event; + + private readonly _onDidChangeOrphaned = this._register(new Emitter()); + readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; + + private readonly _onDispose = this._register(new Emitter()); + readonly onDispose = this._onDispose.event; + + //#endregion + + constructor( + readonly resource: URI, + readonly name: string, + private readonly modelFactory: IFileWorkingCopyModelFactory, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + @ITextFileService private readonly textFileService: ITextFileService, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWorkingCopyService workingCopyService: IWorkingCopyService + ) { + super(); + + if (!fileService.canHandleResource(this.resource)) { + throw new Error(`The file working copy resource ${this.resource.toString(true)} does not have an associated file system provider.`); + } + + // Make known to working copy service + this._register(workingCopyService.registerWorkingCopy(this)); + + this.registerListeners(); + } + + //#region Orphaned Tracking + + private inOrphanMode = false; + + private registerListeners(): void { + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + } + + private async onDidFilesChange(e: FileChangesEvent): Promise { + let fileEventImpactsUs = false; + let newInOrphanModeGuess: boolean | undefined; + + // If we are currently orphaned, we check if the file was added back + if (this.inOrphanMode) { + const fileWorkingCopyResourceAdded = e.contains(this.resource, FileChangeType.ADDED); + if (fileWorkingCopyResourceAdded) { + newInOrphanModeGuess = false; + fileEventImpactsUs = true; + } + } + + // Otherwise we check if the file was deleted + else { + const fileWorkingCopyResourceDeleted = e.contains(this.resource, FileChangeType.DELETED); + if (fileWorkingCopyResourceDeleted) { + newInOrphanModeGuess = true; + fileEventImpactsUs = true; + } + } + + if (fileEventImpactsUs && this.inOrphanMode !== newInOrphanModeGuess) { + let newInOrphanModeValidated: boolean = false; + if (newInOrphanModeGuess) { + // We have received reports of users seeing delete events even though the file still + // exists (network shares issue: https://github.com/microsoft/vscode/issues/13665). + // Since we do not want to mark the working copy as orphaned, we have to check if the + // file is really gone and not just a faulty file event. + await timeout(100); + + if (this.isDisposed()) { + newInOrphanModeValidated = true; + } else { + const exists = await this.fileService.exists(this.resource); + newInOrphanModeValidated = !exists; + } + } + + if (this.inOrphanMode !== newInOrphanModeValidated && !this.isDisposed()) { + this.setOrphaned(newInOrphanModeValidated); + } + } + } + + private setOrphaned(orphaned: boolean): void { + if (this.inOrphanMode !== orphaned) { + this.inOrphanMode = orphaned; + this._onDidChangeOrphaned.fire(); + } + } + + //#endregion + + //#region Dirty + + private dirty = false; + private bufferSavedVersionId: number | undefined; + + isDirty(): this is IResolvedFileWorkingCopy { + return this.dirty; + } + + setDirty(dirty: boolean): void { + if (!this.isResolved()) { + return; // only resolved working copies can be marked dirty + } + + // Track dirty state and version id + const wasDirty = this.dirty; + this.doSetDirty(dirty); + + // Emit as Event if dirty changed + if (dirty !== wasDirty) { + this._onDidChangeDirty.fire(); + } + } + + private doSetDirty(dirty: boolean): () => void { + const wasDirty = this.dirty; + const wasInConflictMode = this.inConflictMode; + const wasInErrorMode = this.inErrorMode; + const oldBufferSavedVersionId = this.bufferSavedVersionId; + + if (!dirty) { + this.dirty = false; + this.inConflictMode = false; + this.inErrorMode = false; + this.updateSavedVersionId(); + } else { + this.dirty = true; + } + + // Return function to revert this call + return () => { + this.dirty = wasDirty; + this.inConflictMode = wasInConflictMode; + this.inErrorMode = wasInErrorMode; + this.bufferSavedVersionId = oldBufferSavedVersionId; + }; + } + + //#endregion + + //#region Resolve + + private lastResolvedFileStat: IFileStatWithMetadata | undefined; + + async resolve(options?: IFileWorkingCopyResolveOptions): Promise> { + this.logService.trace('[file working copy] resolve() - enter', this.resource.toString(true)); + + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[file working copy] resolve() - exit - without resolving because file working copy is disposed', this.resource.toString(true)); + + return this; + } + + // Unless there are explicit contents provided, it is important that we do not + // resolve a working copy that is dirty or is in the process of saving to prevent + // data loss. + if (!options?.contents && (this.dirty || this.saveSequentializer.hasPending())) { + this.logService.trace('[file working copy] resolve() - exit - without resolving because file working copy is dirty or being saved', this.resource.toString(true)); + + return this; + } + + return this.doResolve(options); + } + + private async doResolve(options?: IFileWorkingCopyResolveOptions): Promise> { + + // First check if we have contents to use for the working copy + if (options?.contents) { + return this.resolveFromBuffer(options.contents, options); + } + + // Second, check if we have a backup to resolve from (only for new working copies) + const isNew = !this.isResolved(); + if (isNew) { + const resolvedFromBackup = await this.resolveFromBackup(options); + if (resolvedFromBackup) { + return resolvedFromBackup; + } + } + + // Finally, resolve from file resource + return this.resolveFromFile(options); + } + + private async resolveFromBuffer(buffer: VSBufferReadableStream, options?: IFileWorkingCopyResolveOptions): Promise> { + this.logService.trace('[file working copy] resolveFromBuffer()', this.resource.toString(true)); + + // Try to resolve metdata from disk + let mtime: number; + let ctime: number; + let size: number; + let etag: string; + try { + const metadata = await this.fileService.resolve(this.resource, { resolveMetadata: true }); + mtime = metadata.mtime; + ctime = metadata.ctime; + size = metadata.size; + etag = metadata.etag; + + // Clear orphaned state when resolving was successful + this.setOrphaned(false); + } catch (error) { + + // Put some fallback values in error case + mtime = Date.now(); + ctime = Date.now(); + size = 0; + etag = ETAG_DISABLED; + + // Apply orphaned state based on error code + this.setOrphaned(error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND); + } + + // Resolve with buffer + await this.resolveFromContent({ + resource: this.resource, + name: this.name, + mtime, + ctime, + size, + etag, + value: buffer + }, true /* dirty (resolved from buffer) */); + + return this; + } + + private async resolveFromBackup(options?: IFileWorkingCopyResolveOptions): Promise | undefined> { + + // Resolve backup if any + const backup = await this.backupFileService.resolve(this.resource); + + // Abort if someone else managed to resolve the working copy by now + let isNew = !this.isResolved(); + if (!isNew) { + this.logService.trace('[file working copy] resolveFromBackup() - exit - withoutresolving because previously new file working copy got created meanwhile', this.resource.toString(true)); + + return this; // imply that resolving has happened in another operation + } + + // Try to resolve from backup if we have any + if (backup) { + return this.doResolveFromBackup(backup, options); + } + + // Otherwise signal back that resolving did not happen + return undefined; + } + + private async doResolveFromBackup(backup: IResolvedBackup, options?: IFileWorkingCopyResolveOptions): Promise> { + this.logService.trace('[file working copy] doResolveFromBackup()', this.resource.toString(true)); + + // Resolve with backup + await this.resolveFromContent({ + resource: this.resource, + name: this.name, + mtime: backup.meta ? backup.meta.mtime : Date.now(), + ctime: backup.meta ? backup.meta.ctime : Date.now(), + size: backup.meta ? backup.meta.size : 0, + etag: backup.meta ? backup.meta.etag : ETAG_DISABLED, // etag disabled if unknown! + value: this.textBufferFactoryToStream(backup.value) + }, true /* dirty (resolved from backup) */); + + // Restore orphaned flag based on state + if (backup.meta && backup.meta.orphaned) { + this.setOrphaned(true); + } + + return this; + } + + private async resolveFromFile(options?: IFileWorkingCopyResolveOptions): Promise> { + this.logService.trace('[file working copy] resolveFromFile()', this.resource.toString(true)); + + const forceReadFromDisk = options?.forceReadFromDisk; + + // Decide on etag + let etag: string | undefined; + if (forceReadFromDisk) { + etag = ETAG_DISABLED; // disable ETag if we enforce to read from disk + } else if (this.lastResolvedFileStat) { + etag = this.lastResolvedFileStat.etag; // otherwise respect etag to support caching + } + + // Remember current version before doing any long running operation + // to ensure we are not changing a working copy that was changed + // meanwhile + const currentVersionId = this.versionId; + + // Resolve Content + try { + const content = await this.fileService.readFileStream(this.resource, { etag }); + + // Clear orphaned state when resolving was successful + this.setOrphaned(false); + + // Return early if the working copy content has changed + // meanwhile to prevent loosing any changes + if (currentVersionId !== this.versionId) { + this.logService.trace('[file working copy] resolveFromFile() - exit - without resolving because file working copy content changed', this.resource.toString(true)); + + return this; + } + + return await this.resolveFromContent(content, false /* not dirty (resolved from file) */); + } catch (error) { + const result = error.fileOperationResult; + + // Apply orphaned state based on error code + this.setOrphaned(result === FileOperationResult.FILE_NOT_FOUND); + + // NotModified status is expected and can be handled gracefully + if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { + return this; + } + + // Ignore when a working copy has been resolved once and the file was deleted + // meanwhile. Since we already have the working copy resolved, we can return to + // this state and update the orphaned flag to indicate that this working copy + // has no version on disk anymore. + if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND) { + return this; + } + + // Otherwise bubble up the error + throw error; + } + } + + private async resolveFromContent(content: IFileStreamContent, dirty: boolean): Promise> { + this.logService.trace('[file working copy] resolveFromContent() - enter', this.resource.toString(true)); + + // Return early if we are disposed + if (this.isDisposed()) { + this.logService.trace('[file working copy] resolveFromContent() - exit - because working copy is disposed', this.resource.toString(true)); + + return this; + } + + // Update our resolved disk stat + this.updateLastResolvedFileStat({ + resource: this.resource, + name: content.name, + mtime: content.mtime, + ctime: content.ctime, + size: content.size, + etag: content.etag, + isFile: true, + isDirectory: false, + isSymbolicLink: false + }); + + // Update existing model if we had been resolved + if (this.isResolved()) { + await this.doUpdateModel(content.value); + } + + // Create new model otherwise + else { + await this.doCreateModel(content.value); + } + + // Update working copy dirty flag. This is very important to call + // in both cases of dirty or not because it conditionally updates + // the `bufferSavedVersionId` to determine the version when to consider + // the working copy as saved again (e.g. when undoing back to the + // saved state) + this.setDirty(!!dirty); + + // Emit as event + this._onDidResolve.fire(); + + return this; + } + + private async doCreateModel(contents: VSBufferReadableStream): Promise { + this.logService.trace('[file working copy] doCreateModel()', this.resource.toString(true)); + + // Create model + this._model = await this.modelFactory.createModel(this.resource, contents, CancellationToken.None); + + // Model listeners + this.installModelListeners(this._model); + } + + private ignoreDirtyOnModelContentChange = false; + + private async doUpdateModel(contents: VSBufferReadableStream): Promise { + this.logService.trace('[file working copy] doUpdateModel()', this.resource.toString(true)); + + // Update model value in a block that ignores content change events for dirty tracking + this.ignoreDirtyOnModelContentChange = true; + try { + await this.model?.update(contents, CancellationToken.None); + } finally { + this.ignoreDirtyOnModelContentChange = false; + } + } + + private installModelListeners(model: IFileWorkingCopyModel): void { + + // See https://github.com/microsoft/vscode/issues/30189 + // This code has been extracted to a different method because it caused a memory leak + // where `value` was captured in the content change listener closure scope. + + // Content Change + this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e.isUndoing || e.isRedoing))); + + // Lifecycle + this._register(model.onWillDispose(() => this.dispose())); + } + + private onModelContentChanged(model: IFileWorkingCopyModel, isUndoingOrRedoing: boolean): void { + this.logService.trace(`[file working copy] onModelContentChanged() - enter`, this.resource.toString(true)); + + // In any case increment the version id because it tracks the textual content state of the model at all times + this.versionId++; + this.logService.trace(`[file working copy] onModelContentChanged() - new versionId ${this.versionId}`, this.resource.toString(true)); + + // Remember when the user changed the model through a undo/redo operation. + // We need this information to throttle save participants to fix + // https://github.com/microsoft/vscode/issues/102542 + if (isUndoingOrRedoing) { + this.lastContentChangeFromUndoRedo = Date.now(); + } + + // We mark check for a dirty-state change upon model content change, unless: + // - explicitly instructed to ignore it (e.g. from model.resolve()) + // - the model is readonly (in that case we never assume the change was done by the user) + if (!this.ignoreDirtyOnModelContentChange && !this.isReadonly()) { + + // The contents changed as a matter of Undo and the version reached matches the saved one + // In this case we clear the dirty flag and emit a SAVED event to indicate this state. + if (model.getAlternativeVersionId() === this.bufferSavedVersionId) { + this.logService.trace('[file working copy] onModelContentChanged() - model content changed back to last saved version', this.resource.toString(true)); + + // Clear flags + const wasDirty = this.dirty; + this.setDirty(false); + + // Emit revert event if we were dirty + if (wasDirty) { + this._onDidRevert.fire(); + } + } + + // Otherwise the content has changed and we signal this as becoming dirty + else { + this.logService.trace('[file working copy] onModelContentChanged() - model content changed and marked as dirty', this.resource.toString(true)); + + // Mark as dirty + this.setDirty(true); + } + } + + // Emit as event + this._onDidChangeContent.fire(); + } + + //#endregion + + //#region Backup + + async backup(token: CancellationToken): Promise { + + // Fill in metadata if we are resolved + let meta: IFileWorkingCopyBackupMetaData | undefined = undefined; + if (this.lastResolvedFileStat) { + meta = { + mtime: this.lastResolvedFileStat.mtime, + ctime: this.lastResolvedFileStat.ctime, + size: this.lastResolvedFileStat.size, + etag: this.lastResolvedFileStat.etag, + orphaned: this.inOrphanMode + }; + } + + return { meta, content: this.model?._backupSnapshot() }; + } + + //#endregion + + //#region Save + + private versionId = 0; + + private static readonly UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD = 500; + private lastContentChangeFromUndoRedo: number | undefined = undefined; + + private readonly saveSequentializer = new TaskSequentializer(); + + async save(options: IFileWorkingCopySaveOptions = Object.create(null)): Promise { + if (!this.isResolved()) { + return false; + } + + if (this.isReadonly()) { + this.logService.trace('[file working copy] save() - ignoring request for readonly resource', this.resource.toString(true)); + + return false; // if working copy is readonly we do not attempt to save at all + } + + if ( + (this.hasState(FileWorkingCopyState.CONFLICT) || this.hasState(FileWorkingCopyState.ERROR)) && + (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE) + ) { + this.logService.trace('[file working copy] save() - ignoring auto save request for file working copy that is in conflict or error', this.resource.toString(true)); + + return false; // if working copy is in save conflict or error, do not save unless save reason is explicit + } + + // Actually do save + this.logService.trace('[file working copy] save() - enter', this.resource.toString(true)); + await this.doSave(options); + this.logService.trace('[file working copy] save() - exit', this.resource.toString(true)); + + return true; + } + + private async doSave(options: IFileWorkingCopySaveOptions): Promise { + if (typeof options.reason !== 'number') { + options.reason = SaveReason.EXPLICIT; + } + + let versionId = this.versionId; + this.logService.trace(`[file working copy] doSave(${versionId}) - enter with versionId ${versionId}`, this.resource.toString(true)); + + // Lookup any running pending save for this versionId and return it if found + // + // Scenario: user invoked the save action multiple times quickly for the same contents + // while the save was not yet finished to disk + // + if (this.saveSequentializer.hasPending(versionId)) { + this.logService.trace(`[file working copy] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString(true)); + + return this.saveSequentializer.pending; + } + + // Return early if not dirty (unless forced) + // + // Scenario: user invoked save action even though the working copy is not dirty + if (!options.force && !this.dirty) { + this.logService.trace(`[file working copy] doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource.toString(true)); + + return; + } + + // Return if currently saving by storing this save request as the next save that should happen. + // Never ever must 2 saves execute at the same time because this can lead to dirty writes and race conditions. + // + // Scenario A: auto save was triggered and is currently busy saving to disk. this takes long enough that another auto save + // kicks in. + // Scenario B: save is very slow (e.g. network share) and the user manages to change the buffer and trigger another save + // while the first save has not returned yet. + // + if (this.saveSequentializer.hasPending()) { + this.logService.trace(`[file working copy] doSave(${versionId}) - exit - because busy saving`, this.resource.toString(true)); + + // Indicate to the save sequentializer that we want to + // cancel the pending operation so that ours can run + // before the pending one finishes. + // Currently this will try to cancel pending save + // participants but never a pending save. + this.saveSequentializer.cancelPending(); + + // Register this as the next upcoming save and return + return this.saveSequentializer.setNext(() => this.doSave(options)); + } + + // Push all edit operations to the undo stack so that the user has a chance to + // Ctrl+Z back to the saved version. + if (this.isResolved()) { + this.model.pushStackElement(); + } + + const saveCancellation = new CancellationTokenSource(); + + return this.saveSequentializer.setPending(versionId, (async () => { + + // A save participant can still change the working copy now + // and since we are so close to saving we do not want to trigger + // another auto save or similar, so we block this + // In addition we update our version right after in case it changed + // because of a working copy change + // Save participants can also be skipped through API. + if (this.isResolved() && !options.skipSaveParticipants && this.isTextFileModel(this.model)) { + try { + + // Measure the time it took from the last undo/redo operation to this save. If this + // time is below `UNDO_REDO_SAVE_PARTICIPANTS_THROTTLE_THRESHOLD`, we make sure to + // delay the save participant for the remaining time if the reason is auto save. + // + // This fixes the following issue: + // - the user has configured auto save with delay of 100ms or shorter + // - the user has a save participant enabled that modifies the file on each save + // - the user types into the file and the file gets saved + // - the user triggers undo operation + // - this will undo the save participant change but trigger the save participant right after + // - the user has no chance to undo over the save participant + // + // Reported as: https://github.com/microsoft/vscode/issues/102542 + if (options.reason === SaveReason.AUTO && typeof this.lastContentChangeFromUndoRedo === 'number') { + const timeFromUndoRedoToSave = Date.now() - this.lastContentChangeFromUndoRedo; + if (timeFromUndoRedoToSave < FileWorkingCopy.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD) { + await timeout(FileWorkingCopy.UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD - timeFromUndoRedoToSave); + } + } + + // Run save participants unless save was cancelled meanwhile + if (!saveCancellation.token.isCancellationRequested) { + await this.textFileService.files.runSaveParticipants(this.model, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + } + } catch (error) { + this.logService.error(`[file working copy] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(true)); + } + } + + // It is possible that a subsequent save is cancelling this + // running save. As such we return early when we detect that + // However, we do not pass the token into the file service + // because that is an atomic operation currently without + // cancellation support, so we dispose the cancellation if + // it was not cancelled yet. + if (saveCancellation.token.isCancellationRequested) { + return; + } else { + saveCancellation.dispose(); + } + + // We have to protect against being disposed at this point. It could be that the save() operation + // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot + // be disposed if we are dirty, but if we are not dirty, save() and dispose() can still be triggered + // one after the other without waiting for the save() to complete. If we are disposed(), we risk + // saving contents to disk that are stale (see https://github.com/microsoft/vscode/issues/50942). + // To fix this issue, we will not store the contents to disk when we got disposed. + if (this.isDisposed()) { + return; + } + + // We require a resolved working copy from this point on, since we are about to write data to disk. + if (!this.isResolved()) { + return; + } + + // update versionId with its new value (if pre-save changes happened) + versionId = this.versionId; + + // Clear error flag since we are trying to save again + this.inErrorMode = false; + + // Save to Disk. We mark the save operation as currently pending with + // the latest versionId because it might have changed from a save + // participant triggering + this.logService.trace(`[file working copy] doSave(${versionId}) - before write()`, this.resource.toString(true)); + const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat); + const resolvedFileWorkingCopy = this; + return this.saveSequentializer.setPending(versionId, (async () => { + try { + const stat = await this.fileService.writeFile(lastResolvedFileStat.resource, await resolvedFileWorkingCopy.model.snapshot(CancellationToken.None), { + mtime: lastResolvedFileStat.mtime, + etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource)) ? ETAG_DISABLED : lastResolvedFileStat.etag, + unlock: options.writeUnlock + }); + + this.handleSaveSuccess(stat, versionId, options); + } catch (error) { + this.handleSaveError(error, versionId, options); + } + })()); + })(), () => saveCancellation.cancel()); + } + + private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: IFileWorkingCopySaveOptions): void { + + // Updated resolved stat with updated stat + this.updateLastResolvedFileStat(stat); + + // Update dirty state unless working copy has changed meanwhile + if (versionId === this.versionId) { + this.logService.trace(`[file working copy] handleSaveSuccess(${versionId}) - setting dirty to false because versionId did not change`, this.resource.toString(true)); + this.setDirty(false); + } else { + this.logService.trace(`[file working copy] handleSaveSuccess(${versionId}) - not setting dirty to false because versionId did change meanwhile`, this.resource.toString(true)); + } + + // Update orphan state given save was successful + this.setOrphaned(false); + + // Emit Save Event + this._onDidSave.fire(options.reason ?? SaveReason.EXPLICIT); + } + + private handleSaveError(error: Error, versionId: number, options: IFileWorkingCopySaveOptions): void { + this.logService.error(`[file working copy] handleSaveError(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource.toString(true)); + + // Return early if the save() call was made asking to + // handle the save error itself. + if (options.ignoreErrorHandler) { + throw error; + } + + // In any case of an error, we mark the working copy as dirty to prevent data loss + // It could be possible that the write corrupted the file on disk (e.g. when + // an error happened after truncating the file) and as such we want to preserve + // the working copy contents to prevent data loss. + this.setDirty(true); + + // Flag as error state + this.inErrorMode = true; + + // Look out for a save conflict + if ((error as FileOperationError).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { + this.inConflictMode = true; + } + + // Delegate to save error handler + let throwError = true; + if (this.isTextFileModel(this.model)) { + this.textFileService.files.saveErrorHandler.onSaveError(error, this.model); + throwError = false; + } + + // Emit as event + this._onDidSaveError.fire(); + + if (throwError) { + throw error; + } + } + + private updateSavedVersionId(): void { + + // we remember the models alternate version id to remember when the version + // of the model matches with the saved version on disk. we need to keep this + // in order to find out if the model changed back to a saved version (e.g. + // when undoing long enough to reach to a version that is saved and then to + // clear the dirty flag) + if (this.isResolved()) { + this.bufferSavedVersionId = this.model.getAlternativeVersionId(); + } + } + + private updateLastResolvedFileStat(newFileStat: IFileStatWithMetadata): void { + + // First resolve - just take + if (!this.lastResolvedFileStat) { + this.lastResolvedFileStat = newFileStat; + } + + // Subsequent resolve - make sure that we only assign it if the mtime + // is equal or has advanced. + // This prevents race conditions from resolving and saving. If a save + // comes in late after a revert was called, the mtime could be out of + // sync. + else if (this.lastResolvedFileStat.mtime <= newFileStat.mtime) { + this.lastResolvedFileStat = newFileStat; + } + } + + //#endregion + + //#region Revert + + async revert(options?: IRevertOptions): Promise { + if (!this.isResolved()) { + return; + } + + // Unset flags + const wasDirty = this.dirty; + const undoSetDirty = this.doSetDirty(false); + + // Force read from disk unless reverting soft + const softUndo = options?.soft; + if (!softUndo) { + try { + await this.resolve({ forceReadFromDisk: true }); + } catch (error) { + + // FileNotFound means the file got deleted meanwhile, so ignore it + if ((error as FileOperationError).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + + // Set flags back to previous values, we are still dirty if revert failed + undoSetDirty(); + + throw error; + } + } + } + + // Emit file change event + this._onDidRevert.fire(); + + // Emit dirty change event + if (wasDirty) { + this._onDidChangeDirty.fire(); + } + } + + //#endregion + + //#region State + + private inConflictMode = false; + private inErrorMode = false; + + hasState(state: FileWorkingCopyState): boolean { + switch (state) { + case FileWorkingCopyState.CONFLICT: + return this.inConflictMode; + case FileWorkingCopyState.DIRTY: + return this.dirty; + case FileWorkingCopyState.ERROR: + return this.inErrorMode; + case FileWorkingCopyState.ORPHAN: + return this.inOrphanMode; + case FileWorkingCopyState.PENDING_SAVE: + return this.saveSequentializer.hasPending(); + case FileWorkingCopyState.SAVED: + return !this.dirty; + } + } + + joinState(state: FileWorkingCopyState.PENDING_SAVE): Promise { + return this.saveSequentializer.pending ?? Promise.resolve(); + } + + //#endregion + + //#region Utilities + + isResolved(): this is IResolvedFileWorkingCopy { + return !!this.model; + } + + isReadonly(): boolean { + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + } + + getStat(): IFileStatWithMetadata | undefined { + return this.lastResolvedFileStat; + } + + //#endregion + + //#region Dispose + + private disposed = false; + + isDisposed(): boolean { + return this.disposed; + } + + dispose(): void { + this.logService.trace('[file working copy] dispose()', this.resource.toString(true)); + + // State + this.disposed = true; + this.inConflictMode = false; + this.inOrphanMode = false; + this.inErrorMode = false; + + // Event + this._onDispose.fire(); + + super.dispose(); + } + + //#endregion + + //#region Remainders of text file model world (TODO@bpasero callers have to be handled in a generic way) + + private isTextFileModel(model: unknown): model is ITextFileEditorModel { + const textFileModel = this.textFileService.files.get(this.resource); + + return !!(textFileModel && this.model && (textFileModel as unknown) === (this.model as unknown)); + } + + private textBufferFactoryToStream(factory: ITextBufferFactory): VSBufferReadableStream { + const stream = newWriteableBufferStream(); + + const contents = snapshotToString(factory.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).textBuffer.createSnapshot(false)); + stream.end(VSBuffer.fromString(contents)); + + return stream; + } + + //#endregion +} diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts new file mode 100644 index 00000000000..45d624cd2ff --- /dev/null +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -0,0 +1,421 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import { FileWorkingCopy, IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; +import { SaveReason } from 'vs/workbench/common/editor'; +import { ResourceMap } from 'vs/base/common/map'; +import { ResourceQueue } from 'vs/base/common/async'; +import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { VSBufferReadableStream } from 'vs/base/common/buffer'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +/** + * The only one that should be dealing with `IFileWorkingCopy` and handle all + * operations that are working copy related, such as save/revert, backup + * and resolving. + */ +export interface IFileWorkingCopyManager { + + /** + * An event for when a file working copy was created. + */ + readonly onDidCreate: Event>; + + /** + * An event for when a file working copy was resolved. + */ + readonly onDidResolve: Event>; + + /** + * An event for when a file working copy changed it's dirty state. + */ + readonly onDidChangeDirty: Event>; + + /** + * An event for when a file working copy failed to save. + */ + readonly onDidSaveError: Event>; + + /** + * An event for when a file working copy successfully saved. + */ + readonly onDidSave: Event>; + + /** + * An event for when a file working copy was reverted. + */ + readonly onDidRevert: Event>; + + /** + * Access to all known file working copies within the manager. + */ + readonly workingCopies: IFileWorkingCopy[]; + + /** + * Returns the file working copy for the provided resource + * or undefined if none. + */ + get(resource: URI): IFileWorkingCopy | undefined; + + /** + * Allows to resolve a file working copy. + */ + resolve(resource: URI, options?: IFileWorkingCopyResolveOptions): Promise>; + + /** + * Waits for the file working copy to be ready to be disposed. There may be + * conditions under which the file working copy cannot be disposed, e.g. when + * it is dirty. Once the promise is settled, it is safe to dispose. + */ + canDispose(workingCopy: IFileWorkingCopy): true | Promise; +} + +export interface IFileWorkingCopySaveEvent { + + /** + * The file working copy that was successfully saved. + */ + workingCopy: IFileWorkingCopy; + + /** + * The reason why the file working copy was saved. + */ + reason: SaveReason; +} + +export interface IFileWorkingCopyResolveOptions { + + /** + * The contents to use for the file working copy if known. + * If not provided, the contents will be retrieved from the + * underlying resource or backup if present. + * + * If contents are provided, the file working copy will be marked + * as dirty right from the beginning. + */ + contents?: VSBufferReadableStream; + + /** + * If the file working copy was already resolved before, + * allows to trigger a reload of it to fetch the latest contents: + * - async: resolve() will return immediately and trigger + * a reload that will run in the background. + * - sync: resolve() will only return resolved when the + * file working copy has finished reloading. + */ + reload?: { + async: boolean + }; +} + +export class FileWorkingCopyManager extends Disposable implements IFileWorkingCopyManager { + + //#region Events + + private readonly _onDidCreate = this._register(new Emitter>()); + readonly onDidCreate = this._onDidCreate.event; + + private readonly _onDidResolve = this._register(new Emitter>()); + readonly onDidResolve = this._onDidResolve.event; + + private readonly _onDidChangeDirty = this._register(new Emitter>()); + readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidSaveError = this._register(new Emitter>()); + readonly onDidSaveError = this._onDidSaveError.event; + + private readonly _onDidSave = this._register(new Emitter>()); + readonly onDidSave = this._onDidSave.event; + + private readonly _onDidRevert = this._register(new Emitter>()); + readonly onDidRevert = this._onDidRevert.event; + + //#endregion + + private readonly mapResourceToWorkingCopy = new ResourceMap>(); + private readonly mapResourceToWorkingCopyListeners = new ResourceMap(); + private readonly mapResourceToDisposeListener = new ResourceMap(); + private readonly mapResourceToPendingWorkingCopyResolve = new ResourceMap>>(); + + private readonly workingCopyResolveQueue = this._register(new ResourceQueue()); + + get workingCopies(): IFileWorkingCopy[] { + return [...this.mapResourceToWorkingCopy.values()]; + } + + constructor( + private readonly modelFactory: IFileWorkingCopyModelFactory, + @IFileService private readonly fileService: IFileService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Update working copies from file change events + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Lifecycle + this.lifecycleService.onShutdown(() => this.dispose); + } + + private onDidFilesChange(e: FileChangesEvent): void { + for (const workingCopy of this.workingCopies) { + if (workingCopy.isDirty() || !workingCopy.isResolved()) { + continue; // require a resolved, saved working copy to continue + } + + // Trigger a resolve for any update or add event that impacts + // the working copy. We also consider the added event + // because it could be that a file was added and updated + // right after. + if (e.contains(workingCopy.resource, FileChangeType.UPDATED, FileChangeType.ADDED)) { + this.queueWorkingCopyResolve(workingCopy); + } + } + } + + private queueWorkingCopyResolve(workingCopy: IFileWorkingCopy): void { + + // Resolves a working copy to update (use a queue to prevent accumulation of + // resolve when the resolving actually takes long. At most we only want the + // queue to have a size of 2 (1 running resolve and 1 queued resolve). + const queue = this.workingCopyResolveQueue.queueFor(workingCopy.resource); + if (queue.size <= 1) { + queue.queue(async () => { + try { + await workingCopy.resolve(); + } catch (error) { + onUnexpectedError(error); + } + }); + } + } + + get(resource: URI): IFileWorkingCopy | undefined { + return this.mapResourceToWorkingCopy.get(resource); + } + + async resolve(resource: URI, options?: IFileWorkingCopyResolveOptions): Promise> { + + // Await a pending working copy resolve first before proceeding + // to ensure that we never resolve a working copy more than once + // in parallel + const pendingResolve = this.joinPendingResolve(resource); + if (pendingResolve) { + await pendingResolve; + } + + let workingCopyPromise: Promise>; + let workingCopy = this.get(resource); + let didCreateWorkingCopy = false; + + // Working copy exists + if (workingCopy) { + + // Always reload if contents are provided + if (options?.contents) { + workingCopyPromise = workingCopy.resolve(options); + } + + // Reload async or sync based on options + else if (options?.reload) { + + // async reload: trigger a reload but return immediately + if (options.reload.async) { + workingCopyPromise = Promise.resolve(workingCopy); + workingCopy.resolve(options); + } + + // sync reload: do not return until working copy reloaded + else { + workingCopyPromise = workingCopy.resolve(options); + } + } + + // Do not reload + else { + workingCopyPromise = Promise.resolve(workingCopy); + } + } + + // File working copy does not exist + else { + didCreateWorkingCopy = true; + + const newWorkingCopy = workingCopy = this.instantiationService.createInstance(FileWorkingCopy, resource, this.labelService.getUriBasenameLabel(resource), this.modelFactory) as unknown as IFileWorkingCopy; + workingCopyPromise = workingCopy.resolve(options); + + this.registerWorkingCopy(newWorkingCopy); + } + + // Store pending resolve to avoid race conditions + this.mapResourceToPendingWorkingCopyResolve.set(resource, workingCopyPromise); + + // Make known to manager (if not already known) + this.add(resource, workingCopy); + + // Emit some events if we created the working copy + if (didCreateWorkingCopy) { + this._onDidCreate.fire(workingCopy); + + // If the working copy is dirty right from the beginning, + // make sure to emit this as an event + if (workingCopy.isDirty()) { + this._onDidChangeDirty.fire(workingCopy); + } + } + + try { + const resolvedWorkingCopy = await workingCopyPromise; + + // Remove from pending resolves + this.mapResourceToPendingWorkingCopyResolve.delete(resource); + + // File working copy can be dirty if a backup was restored, so we make sure to + // have this event delivered if we created the working copy here + if (didCreateWorkingCopy && resolvedWorkingCopy.isDirty()) { + this._onDidChangeDirty.fire(resolvedWorkingCopy); + } + + return resolvedWorkingCopy; + } catch (error) { + + // Free resources of this invalid working copy + if (workingCopy) { + workingCopy.dispose(); + } + + // Remove from pending resolves + this.mapResourceToPendingWorkingCopyResolve.delete(resource); + + throw error; + } + } + + private joinPendingResolve(resource: URI): Promise | undefined { + const pendingWorkingCopyResolve = this.mapResourceToPendingWorkingCopyResolve.get(resource); + if (pendingWorkingCopyResolve) { + return pendingWorkingCopyResolve.then(undefined, error => {/* ignore any error here, it will bubble to the original requestor*/ }); + } + + return undefined; + } + + private registerWorkingCopy(workingCopy: IFileWorkingCopy): void { + + // Install working copy listeners + const workingCopyListeners = new DisposableStore(); + workingCopyListeners.add(workingCopy.onDidResolve(() => this._onDidResolve.fire(workingCopy))); + workingCopyListeners.add(workingCopy.onDidChangeDirty(() => this._onDidChangeDirty.fire(workingCopy))); + workingCopyListeners.add(workingCopy.onDidSaveError(() => this._onDidSaveError.fire(workingCopy))); + workingCopyListeners.add(workingCopy.onDidSave(reason => this._onDidSave.fire({ workingCopy: workingCopy, reason }))); + workingCopyListeners.add(workingCopy.onDidRevert(() => this._onDidRevert.fire(workingCopy))); + + // Keep for disposal + this.mapResourceToWorkingCopyListeners.set(workingCopy.resource, workingCopyListeners); + } + + protected add(resource: URI, workingCopy: IFileWorkingCopy): void { + const knownWorkingCopy = this.mapResourceToWorkingCopy.get(resource); + if (knownWorkingCopy === workingCopy) { + return; // already cached + } + + // dispose any previously stored dispose listener for this resource + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + disposeListener.dispose(); + } + + // store in cache but remove when working copy gets disposed + this.mapResourceToWorkingCopy.set(resource, workingCopy); + this.mapResourceToDisposeListener.set(resource, workingCopy.onDispose(() => this.remove(resource))); + } + + protected remove(resource: URI): void { + this.mapResourceToWorkingCopy.delete(resource); + + const disposeListener = this.mapResourceToDisposeListener.get(resource); + if (disposeListener) { + dispose(disposeListener); + this.mapResourceToDisposeListener.delete(resource); + } + + const workingCopyListener = this.mapResourceToWorkingCopyListeners.get(resource); + if (workingCopyListener) { + dispose(workingCopyListener); + this.mapResourceToWorkingCopyListeners.delete(resource); + } + } + + clear(): void { + + // working copy caches + this.mapResourceToWorkingCopy.clear(); + this.mapResourceToPendingWorkingCopyResolve.clear(); + + // dispose the dispose listeners + this.mapResourceToDisposeListener.forEach(listener => listener.dispose()); + this.mapResourceToDisposeListener.clear(); + + // dispose the working copy change listeners + this.mapResourceToWorkingCopyListeners.forEach(listener => listener.dispose()); + this.mapResourceToWorkingCopyListeners.clear(); + } + + canDispose(workingCopy: IFileWorkingCopy): true | Promise { + + // quick return if working copy already disposed or not dirty and not resolving + if ( + workingCopy.isDisposed() || + (!this.mapResourceToPendingWorkingCopyResolve.has(workingCopy.resource) && !workingCopy.isDirty()) + ) { + return true; + } + + // promise based return in all other cases + return this.doCanDispose(workingCopy); + } + + private async doCanDispose(workingCopy: IFileWorkingCopy): Promise { + + // if we have a pending working copy resolve, await it first and then try again + const pendingResolve = this.joinPendingResolve(workingCopy.resource); + if (pendingResolve) { + await pendingResolve; + + return this.canDispose(workingCopy); + } + + // dirty working copy: we do not allow to dispose dirty working copys + // to prevent data loss cases. dirty working copys can only be disposed when + // they are either saved or reverted + if (workingCopy.isDirty()) { + await Event.toPromise(workingCopy.onDidChangeDirty); + + return this.canDispose(workingCopy); + } + + return true; + } + + dispose(): void { + super.dispose(); + + this.clear(); + } +} diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index a390c9be0dd..09f1680edd5 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -46,6 +46,19 @@ export interface IWorkingCopyBackup { content?: ITextSnapshot; } +/** + * A working copy is an abstract concept to unify handling of + * data that can be worked on (e.g. edited) in an editor. + * + * Every working copy has in common that it is identified by + * a resource `URI` and only one working copy can be registered + * with the same `URI`. + * + * A working copy resource may be the backing store of the data + * (e.g. a file on disk), but that is not a requirement. The + * `URI` is mainly used to uniquely identify a working copy among + * others. + */ export interface IWorkingCopy { /** @@ -160,9 +173,11 @@ export interface IWorkingCopyService { /** * Register a new working copy with the service. This method will - * throw if you try to register a working copy with a resource - * that was already registered before. There can only be 1 working - * copy per resource registered to the service. + * throw if you try to register a working copy on a resource that + * has already been registered. + * + * Overall there can only ever be 1 working copy with the same + * resource. */ registerWorkingCopy(workingCopy: IWorkingCopy): IDisposable; diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 7d60bff7703..607d6c29305 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -16,6 +16,7 @@ import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; import { EditorModel } from 'vs/workbench/common/editor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { dirname, resolve } from 'vs/base/common/path'; +import { canceled } from 'vs/base/common/errors'; export const WORKSPACE_TRUST_ENABLED = 'workspace.trustEnabled'; export const WORKSPACE_TRUST_STORAGE_KEY = 'content.trust.model.key'; @@ -191,6 +192,9 @@ export class WorkspaceTrustRequestModel extends Disposable implements IWorkspace private readonly _onDidCompleteRequest = this._register(new Emitter()); readonly onDidCompleteRequest = this._onDidCompleteRequest.event; + private readonly _onDidCancelRequest = this._register(new Emitter()); + readonly onDidCancelRequest = this._onDidCancelRequest.event; + initiateRequest(request: WorkspaceTrustRequest): void { if (this.trustRequest && (!request.modal || this.trustRequest.modal)) { return; @@ -204,6 +208,11 @@ export class WorkspaceTrustRequestModel extends Disposable implements IWorkspace this.trustRequest = undefined; this._onDidCompleteRequest.fire(trustState); } + + cancelRequest(): void { + this.trustRequest = undefined; + this._onDidCancelRequest.fire(); + } } export class WorkspaceTrustService extends Disposable implements IWorkspaceTrustService { @@ -217,8 +226,11 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust readonly onDidChangeTrustState = this._onDidChangeTrustState.event; private _currentTrustState: WorkspaceTrustState = WorkspaceTrustState.Unknown; - private _inFlightResolver?: (trustState: WorkspaceTrustState) => void; private _trustRequestPromise?: Promise; + private _inFlightResolver?: (trustState: WorkspaceTrustState) => void; + private _modalTrustRequestPromise?: Promise; + private _modalTrustRequestResolver?: (trustState: WorkspaceTrustState) => void; + private _modalTrustRequestRejecter?: (error: Error) => void; private _workspace: IWorkspace; private readonly _ctxWorkspaceTrustState: IContextKey; @@ -241,8 +253,10 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust this.logInitialWorkspaceTrustInfo(); + this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => this.currentTrustState = this.calculateWorkspaceTrustState())); this._register(this.dataModel.onDidChangeTrustState(() => this.currentTrustState = this.calculateWorkspaceTrustState())); this._register(this.requestModel.onDidCompleteRequest((trustState) => this.onTrustRequestCompleted(trustState))); + this._register(this.requestModel.onDidCancelRequest(() => this.onTrustRequestCancelled())); this._ctxWorkspaceTrustState = WorkspaceTrustContext.TrustState.bindTo(contextKeyService); this._ctxWorkspaceTrustPendingRequest = WorkspaceTrustContext.PendingRequest.bindTo(contextKeyService); @@ -358,6 +372,9 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust } private onTrustRequestCompleted(trustState?: WorkspaceTrustState): void { + if (this._modalTrustRequestResolver) { + this._modalTrustRequestResolver(trustState === undefined ? this.currentTrustState : trustState); + } if (this._inFlightResolver) { this._inFlightResolver(trustState === undefined ? this.currentTrustState : trustState); } @@ -365,6 +382,10 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust this._inFlightResolver = undefined; this._trustRequestPromise = undefined; + this._modalTrustRequestResolver = undefined; + this._modalTrustRequestRejecter = undefined; + this._modalTrustRequestPromise = undefined; + if (trustState === undefined) { return; } @@ -377,6 +398,16 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust this._ctxWorkspaceTrustState.set(trustState); } + private onTrustRequestCancelled(): void { + if (this._modalTrustRequestRejecter) { + this._modalTrustRequestRejecter(canceled()); + } + + this._modalTrustRequestResolver = undefined; + this._modalTrustRequestRejecter = undefined; + this._modalTrustRequestPromise = undefined; + } + getWorkspaceTrustState(): WorkspaceTrustState { return this.currentTrustState; } @@ -386,31 +417,42 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust } async requireWorkspaceTrust(request: WorkspaceTrustRequest = { modal: true }): Promise { + // Trusted workspace if (this.currentTrustState === WorkspaceTrustState.Trusted) { return this.currentTrustState; } + // Untrusted workspace - soft request if (this.currentTrustState === WorkspaceTrustState.Untrusted && !request.modal) { return this.currentTrustState; } - if (this._trustRequestPromise) { - if (request.modal && - this.requestModel.trustRequest && - !this.requestModel.trustRequest.modal) { - this.requestModel.initiateRequest(request); + if (request.modal) { + // Modal request + if (!this._modalTrustRequestPromise) { + // Create promise + this._modalTrustRequestPromise = new Promise((resolve, reject) => { + this._modalTrustRequestResolver = resolve; + this._modalTrustRequestRejecter = reject; + }); + } else { + // Return existing promises + return this._modalTrustRequestPromise; + } + } else { + // Soft request + if (!this._trustRequestPromise) { + this._trustRequestPromise = new Promise(resolve => { + this._inFlightResolver = resolve; + }); + } else { + return this._trustRequestPromise; } - - return this._trustRequestPromise; } - this._trustRequestPromise = new Promise(resolve => { - this._inFlightResolver = resolve; - }); - this.requestModel.initiateRequest(request); this._ctxWorkspaceTrustPendingRequest.set(true); - return this._trustRequestPromise; + return request.modal ? this._modalTrustRequestPromise! : this._trustRequestPromise!; } } diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index b40a45b3539..9e29d3ac043 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -591,7 +591,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Invoke }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 2); const [first, second] = actions; assert.strictEqual(first.action.title, 'Testing1'); @@ -615,7 +615,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Invoke }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 1); const [first] = actions; assert.strictEqual(first.action.title, 'Testing1'); @@ -638,7 +638,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Invoke }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 1); }); @@ -656,7 +656,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Invoke }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 1); }); diff --git a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts index 0480a1563ab..7494f528987 100644 --- a/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts @@ -10,7 +10,7 @@ import * as fs from 'fs'; import * as pfs from 'vs/base/node/pfs'; import * as path from 'vs/base/common/path'; import * as assert from 'assert'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { CancellationToken } from 'vs/base/common/cancellation'; import { RequestService } from 'vs/platform/request/node/requestService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 51f73dc2c01..29b0815ed41 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -6,7 +6,6 @@ import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService, TestPathService, TestEncodingOracle, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { FileOperationError, IFileService } from 'vs/platform/files/common/files'; @@ -14,7 +13,7 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IDialogService, IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -40,9 +39,11 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; -import { homedir, release } from 'os'; +import { homedir, release, tmpdir } from 'os'; import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; +import { INativeEnvironmentPaths } from 'vs/platform/environment/common/environmentService'; export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = { windowId: 0, @@ -60,7 +61,9 @@ export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = { ...parseArgs(process.argv, OPTIONS) }; -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration, TestProductService); +export const TestEnvironmentPaths: INativeEnvironmentPaths = { homeDir: homedir(), tmpDir: tmpdir(), userDataDir: getUserDataPath(TestWorkbenchConfiguration) }; + +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration, TestEnvironmentPaths, TestProductService); export class TestTextFileService extends NativeTextFileService { private resolveTextContentError!: FileOperationError | null; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 2fd80a3c538..3f9d179715b 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -80,6 +80,7 @@ import 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScann import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService'; import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import 'vs/workbench/services/notification/common/notificationService'; +import 'vs/workbench/services/userDataSync/browser/userDataSyncResourceEnablementService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -118,12 +119,9 @@ import { IDownloadService } from 'vs/platform/download/common/download'; import { DownloadService } from 'vs/platform/download/common/downloadService'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IUserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync'; -registerSingleton(IUserDataSyncResourceEnablementService, UserDataSyncResourceEnablementService); registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); registerSingleton(IExtensionsStorageSyncService, ExtensionsStorageSyncService); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 6ca7f49bc65..7d3a06dd857 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -224,6 +224,12 @@ interface ISettingsSyncOptions { */ readonly enabled: boolean; + /** + * Version of extensions sync state. + * Extensions sync state will be reset if version is provided and different from previous version. + */ + readonly extensionsSyncStateVersion?: string; + /** * Handler is being called when the user changes Settings Sync enablement. */ diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index e8dae86262b..8d0c2468705 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -25,34 +25,34 @@ export function setup() { await app.workbench.quickaccess.runCommand('workbench.action.closeActiveEditor'); }); - it('inserts/edits code cell', async function () { - const app = this.app as Application; - await app.workbench.notebook.openNotebook(); - await app.workbench.notebook.focusNextCell(); - await app.workbench.notebook.insertNotebookCell('code'); - await app.workbench.notebook.waitForTypeInEditor('// some code'); - await app.workbench.notebook.stopEditingCell(); - }); + // it('inserts/edits code cell', async function () { + // const app = this.app as Application; + // await app.workbench.notebook.openNotebook(); + // await app.workbench.notebook.focusNextCell(); + // await app.workbench.notebook.insertNotebookCell('code'); + // await app.workbench.notebook.waitForTypeInEditor('// some code'); + // await app.workbench.notebook.stopEditingCell(); + // }); - it('inserts/edits markdown cell', async function () { - const app = this.app as Application; - await app.workbench.notebook.openNotebook(); - await app.workbench.notebook.focusNextCell(); - await app.workbench.notebook.insertNotebookCell('markdown'); - await app.workbench.notebook.waitForTypeInEditor('## hello2! '); - await app.workbench.notebook.stopEditingCell(); - await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!'); - }); + // it('inserts/edits markdown cell', async function () { + // const app = this.app as Application; + // await app.workbench.notebook.openNotebook(); + // await app.workbench.notebook.focusNextCell(); + // await app.workbench.notebook.insertNotebookCell('markdown'); + // await app.workbench.notebook.waitForTypeInEditor('## hello2! '); + // await app.workbench.notebook.stopEditingCell(); + // await app.workbench.notebook.waitForMarkdownContents('h2', 'hello2!'); + // }); - it('moves focus as it inserts/deletes a cell', async function () { - const app = this.app as Application; - await app.workbench.notebook.openNotebook(); - await app.workbench.notebook.insertNotebookCell('code'); - await app.workbench.notebook.waitForActiveCellEditorContents(''); - await app.workbench.notebook.stopEditingCell(); - await app.workbench.notebook.deleteActiveCell(); - await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell'); - }); + // it('moves focus as it inserts/deletes a cell', async function () { + // const app = this.app as Application; + // await app.workbench.notebook.openNotebook(); + // await app.workbench.notebook.insertNotebookCell('code'); + // await app.workbench.notebook.waitForActiveCellEditorContents(''); + // await app.workbench.notebook.stopEditingCell(); + // await app.workbench.notebook.deleteActiveCell(); + // await app.workbench.notebook.waitForMarkdownContents('p', 'Markdown Cell'); + // }); it.skip('moves focus in and out of output', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/113882 const app = this.app as Application; diff --git a/yarn.lock b/yarn.lock index 1cd880523e2..a906f56e968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10096,10 +10096,10 @@ vscode-oniguruma@1.3.1: resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f" integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ== -vscode-proxy-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.6.0.tgz#3ea5c3c82f7abe945d690eb34b2c877cf5833f12" - integrity sha512-dvoLmEO/IxkbcNrRHH6ey8ITfvau4wDg01S+iAJ5Pq/FoAl2ZeE4cK5VEnQ2JHqM20kTLhyZfkjdHq6l7/T+xA== +vscode-proxy-agent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.7.0.tgz#adf19dfbc9beba508b1c72a83cd64b5d1cb9d84c" + integrity sha512-eXGwlzwQJJqutYHjhC0UfBw1Sp8p2UygXACQjMSMKkIufeF68c5Z12s7v7UVBkqW3KfgEh/zDaO/bPkWLI/RZw== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0"